Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Time instead of DateTime #1952

Closed
bquorning opened this issue Jun 6, 2015 · 33 comments
Closed

Use Time instead of DateTime #1952

bquorning opened this issue Jun 6, 2015 · 33 comments

Comments

@bquorning
Copy link
Contributor

A recent gist (https://gist.github.com/pixeltrix/e2298822dd89d854444b, via This week in Rails) explains why you can almost always use Time or Date instead of DateTime.

Without having dug very deep, it looks like you can use one of the simpler classes unless a DateTime method is being called with a start argument such as Date::ENGLAND, Date::GREGORIAN, Date::ITALY or Date::JULIAN.

Should we have a (rails or style) cop that check for uses of DateTime where a simple Date or Time class would suffice?

@bbatsov
Copy link
Collaborator

bbatsov commented Jun 7, 2015

Makes sense to me. We also need an entry in the Ruby style guide for this, as it's not something Rails-specific.

@studiolo
Copy link

Hi, I saw this on Ruby issues. If there is no hurry I'd like to work on this task. It will help me to improve my knowledge of Ruby, Rubocop, testing and Git. I'll read the contributing info, sample cops source etc., and see how far I can get. Where can I turn to if I have Rubocop specific questions?

Best,
s.

@alexdowad
Copy link
Contributor

Where can I turn to if I have Rubocop specific questions?

Post them right here, in this thread?

@studiolo
Copy link

You're right :-)

@studiolo
Copy link

studiolo commented Mar 4, 2016

Hi,
how do I use Rubocop in 'developer mode'? Check out the latest version and then run ./bin/rubocop? Or do I run the repl target from the Rakefile?

Thank you!

@alexdowad
Copy link
Contributor

Personally I usually run it as ruby -Ilib bin/rubocop (first cd into the project directory).

@studiolo
Copy link

studiolo commented Mar 6, 2016

Thanks! I think now my first task is to research what I should put into the Ruby Style Guide about choosing between Time or Date and DateTime. Only then can I start implementing the cop. And am I to put it into the performance or the style directory?

@Drenmi
Copy link
Collaborator

Drenmi commented Jun 25, 2016

@studiolo: Are you still working on this? Mind if I pick it up?

@studiolo
Copy link

@Drenmi: I'm afraid I have let this slide. I'd be glad if you picked it up :-)

@AlexWayfer
Copy link
Contributor

I don't understand this cop.

The original problem from gist:

>> shakespeare = DateTime.iso8601('1616-04-23', Date::ENGLAND)
=> Tue, 23 Apr 1616 00:00:00 +0000
>> cervantes = DateTime.iso8601('1616-04-23', Date::ITALY)
=> Sat, 23 Apr 1616 00:00:00 +0000

(incorrect code/output, but OK)

The warning from cop:

Prefer Date or Time over DateTime.

Okay, let's use Date!

pry: main > DateTime.iso8601('1616-04-23', Date::ENGLAND).strftime('%c')
=> "Tue Apr 23 00:00:00 1616"
pry: main > DateTime.iso8601('1616-04-23', Date::ITALY).strftime('%c')
=> "Sat Apr 23 00:00:00 1616"
pry: main > Date.iso8601('1616-04-23', Date::ENGLAND).strftime('%c')
=> "Tue Apr 23 00:00:00 1616"
pry: main > Date.iso8601('1616-04-23', Date::ITALY).strftime('%c')
=> "Sat Apr 23 00:00:00 1616"

Oh, the same result…

So…?

@dpostorivo
Copy link
Contributor

If that's happening that's a bug. It should not report an offense if a historic date is used. When making the cop I tested against the examples in the ruby style guide which all worked. I'll dig into this.

jfly added a commit to thewca/worldcubeassociation.org that referenced this issue Oct 19, 2017
jfly added a commit to thewca/worldcubeassociation.org that referenced this issue Oct 19, 2017
@rmoriz
Copy link

rmoriz commented Jan 25, 2018

I don't understand this cop either.

I'm using DateTime.now.to_s a lot to create ISO6801 timestamps without whitespace:

irb(main):024:0> DateTime.now.to_s
=> "2018-01-25T14:42:28+01:00"
irb(main):025:0> Time.now.to_s
=> "2018-01-25 14:42:30 +0100"

As you can see, Time#to_string is not ISO6801 compliant by default unless one does not need the T as Date/Time separator and can deal with whitespace.

@baelter
Copy link

baelter commented Feb 28, 2018

Also disabling this. DateTime makes it easier to compare now with Date.

@AlexWayfer
Copy link
Contributor

What's point of this issue if Date.iso8601 has the same output as DateTime.iso8601?

I think it should be reverted.

@jarredholman
Copy link

Also confused about this cop. There is no explanation here about why Time is supposedly better than DateTime.

@fwitzke
Copy link

fwitzke commented Apr 26, 2018

Ruby docs on DateTime provide a good explanation.

@AlexWayfer
Copy link
Contributor

Ruby docs on DateTime provide a good explanation.

But no about difference between DateTime and Time in this question.

@Mifrill
Copy link

Mifrill commented Jun 21, 2018

But no about difference between DateTime and Time in this question.

I'm not sure about this

(byebug) Time.parse("Ends from 28 Jun 2018 12:00 BST").utc.to_s

"2018-06-28 09:00:00 UTC"

(byebug) Date.parse("Ends from 28 Jun 2018 12:00 BST").to_time.utc.to_s

"2018-06-27 21:00:00 UTC"

(byebug) DateTime.parse("Ends from 28 Jun 2018 12:00 BST").to_time.utc.to_s

"2018-06-28 11:00:00 UTC"

@AlexWayfer
Copy link
Contributor

AlexWayfer commented Jul 1, 2018

@Mifrill, emm:

Time.parse("Ends from 28 Jun 2018 12:00 BST").utc.to_s
NoMethodError: undefined method `parse' for Time:Class

https://repl.it/repls/SpeedySeagreenProjector

Date and DateTime of course will be different because Date has no time, and #to_time assigns 0:00, not 12:00 BST (it lost).

@NobodysNightmare
Copy link
Contributor

Is there any case where using DateTime would have been reasonable, that now makes using Date the right option? I assume that you would always want to switch to Time when you used DateTime before, no?

If so, the cops message could be clarified to prefer Time over DateTime...


@rmoriz wrote:

I'm using DateTime.now.to_s a lot to create ISO6801 timestamps without whitespace

How about Time.now.iso8601?

@AlexWayfer
Copy link
Contributor

I got it!

(You need to require 'time for Time.parse)

https://repl.it/@AlexWayfer/AquaAmbitiousProspect

So,

DateTime.parse("28 Jun 2018 12:00 BST").to_time.utc.to_s # => 2018-06-28 12:00:00 +0100
# or 2018-06-28 11:00:00 UTC, and this is correct

Time.parse("28 Jun 2018 12:00 BST").utc.to_s # => 2018-06-28 12:00:00 +0000
# or 2018-06-28 12:00:00 UTC, and this is not correct

I think this cop should be totally removed.

@bdewater
Copy link
Contributor

From the Ruby docs regarding Time.parse:

Since there are numerous conflicts among locally defined time zone abbreviations all over the world, this method is not intended to understand all of them.

A modified example using a time zone abbreviation listed in lib/time.rb works as expected:

DateTime.parse("28 Jun 2018 12:00 EST").to_time.utc.to_s
=> "2018-06-28 17:00:00 UTC"

Time.parse("28 Jun 2018 12:00 EST").utc.to_s
=> "2018-06-28 17:00:00 UTC"

So maybe this would be better fixed upstream. Same thing with the different to_s behaviour?

@AlexWayfer
Copy link
Contributor

So maybe this would be better fixed upstream.

Or maybe RuboCop should not enforce imperfect class instead of more complete.

Same thing with the different to_s behaviour?

I don't understand the question.

@bdewater
Copy link
Contributor

I don't understand the question.

I meant that if you feel Time#to_s should output ISO8601 formatted data, that could be fixed upstream. I don't think it's much of a reason for debating the existence of this cop, because Time#iso8601 is available.

Or maybe RuboCop should not enforce imperfect class instead of more complete.

You don't have to account for timezones? Because that's exactly the argument why DateTime is often not the right choice:

require "time"
time = Time.parse "2018-03-11 1:00:00" # OS set to Eastern Standard Time
=> 2018-03-11 01:00:00 -0500
time.dst?
=> false
summer_time = time + 60*60 # advanced one hour
=> 2018-03-11 03:00:00 -0400 
summer_time.dst?
=> true

datetime = DateTime.parse "2018-03-11 1:00:00 -05:00"
=> #<DateTime: 2018-03-11T01:00:00-05:00 ((2458189j,21600s,0n),-18000s,2299161j)>
datetime.dst? # this doesn't work
NoMethodError: undefined method `dst?' for #<DateTime:0x00007fbc2010c258>
summer_datetime = datetime + (1.0/24) # advanced one hour
=> #<DateTime: 2018-03-11T02:00:00-05:00 ((2458189j,25200s,0n),-18000s,2299161j)>
# UTC offset wise this is Jamaica instead of Montreal

summer_time.to_datetime # same time, 1 hour different UTC offsets
=> #<DateTime: 2018-03-11T03:00:00-04:00 ((2458189j,25200s,0n),-14400s,2299161j)>

In #6189 you asked me for an example of a bug if you mix the two classes - memory is a bit hazy since it's a couple of years ago but the problem was basically "off by 1 hour" around DST transitions because the frontend didn't show timezones or UTC offsets. Something like:

summer_time.strftime "%Y-%m-%d %H:%M:%S"
=> "2018-03-11 03:00:00"
summer_datetime.strftime "%Y-%m-%d %H:%M:%S"
=> "2018-03-11 02:00:00"

The other argument Time.parse knowing less timezone abbreviations then DateTime's variant, like BST. There's however a problem with abbreviation approach, the docs have an example for CST:

-06:00 in America/Chicago,
-05:00 in America/Havana,
+08:00 in Asia/Harbin,
+09:30 in Australia/Darwin,
+10:30 in Australia/Adelaide,

Both Time and DateTime pick the first option (Eastern Standard Time) when asked to parse "28 Jun 2018 12:00 CST" - the reason being RFC 2822 - which also happens to declare all abbreviations obsolete.

The solution to unambiguously refer to a timezone is to refer to the UTC offset, which means you'll never have problems with parsing time, date and timezone from string either:

string = Time.now.iso8601
=> "2018-08-19T23:31:21-04:00"
time = Time.iso8601 string
=> 2018-08-19 23:31:21 -0400
time.dst?
=> true

Imperfect as you might find Time's interface to be, I think it does a better job 99% of the time than DateTime, and the issues you mentioned can be easily worked around.

@AlexWayfer
Copy link
Contributor

You don't have to account for timezones? Because that's exactly the argument why DateTime is often not the right choice:

I have to account. This makes more sense, thanks. But do you know about "wall time"? Like timestamp vs timestamptz in PostgreSQL. DateTime can be good choice for this.

The solution to unambiguously refer to a timezone is to refer to the UTC offset

Danger: you're loosing timezone info and summer times:

> Time.parse('2018-03-11 03:00:00 -0400').dst?
=> false
> Time.parse('2018-08-19 23:31:21 -0400').dst?
=> false

Oh, stop, WTF?

> time = Time.parse('2018-03-11 01:00:00 -0500')
=> 2018-03-11 01:00:00 -0500
> time.dst?
=> false
> summer_time = time + 3600
=> 2018-03-11 02:00:00 -0500
> summer_time.dst?
=> false

The same with EST instead of -0500.

@Mifrill
Copy link

Mifrill commented Aug 20, 2018

I think it's best to disable this cop by default, and not completely delete it

@bdewater
Copy link
Contributor

bdewater commented Aug 20, 2018

Danger: you're loosing timezone info and summer times:

What environment are you seeing this on? These examples return true for me on 2.3.7, 2.4.4 and 2.5.1 on MacOS 10.13.6 🤔

I have to account. This makes more sense, thanks. But do you know about "wall time"?

Better here is not to use DateTime or Time since your results can still be influenced if the system time changes. Process.clock_gettime(Process::CLOCK_MONOTONIC) doesn't have that problem, see e.g. benchmark-ips source with a fallback for non-MRI implementations that don't have this API.

@AlexWayfer
Copy link
Contributor

What environment are you seeing this on? These examples return true for me on 2.3.7, 2.4.4 and 2.5.1 on MacOS 10.13.6 thinking

> ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]

Better here is not to use DateTime or Time since your results can still be influenced if the system time changes.

What? First you write "DateTime.parse doesn't account timezones", then "better is not to use for time without timezones".

@bdewater
Copy link
Contributor

Sorry, I'm not sure what's going on with your environment here.

What? First you write "DateTime.parse doesn't account timezones", then "better is not to use for time without timezones".

I assumed with 'wall time' you wanted to measure elapsed time. You could use Time.now.to_f to get the seconds since the Unix epoch at start and end, and since that's UTC you don't have to care about timezones or daylight savings. But your system clock can still be changed by things such as NTP corrections, so for the most accurate result you need monotonic time. But perhaps you meant something else?

@AlexWayfer
Copy link
Contributor

Sorry, I'm not sure what's going on with your environment here.

So, why we should enforce one class instead of another, if you even can't describe different behavior in different computers?

Please, everybody, have strong (simple) arguments before enforcing something.

I assumed with 'wall time' you wanted to measure elapsed time.

Or future event.

"Plan a review of work day at 19:00, independently of your timezone".

@bdewater
Copy link
Contributor

So, why we should enforce one class instead of another, if you even can't describe different behavior in different computers? Please, everybody, have strong (simple) arguments before enforcing something.

This Stack Overflow answer goes into detail where the data comes from. Why it does not work as expected on your environment is a separate issue outside of Rubocop. The argument stays the same: DateTime does not understand timezones and daylight savings, Time does.

Or future event. "Plan a review of work day at 19:00, independently of your timezone".

So it's a timestamp (based on the Postgres example you gave earlier) in which case I do it at that time (in the timezone)? Or it's a recurring event that comes with it's own set of challenges around daylight savings time?

@AlexWayfer
Copy link
Contributor

This Stack Overflow answer goes into detail where the data comes from.

It's about Time.now, not Time.parse with offset in the String.

The argument stays the same: DateTime does not understand timezones and daylight savings, Time does.

Your example with Time is not reproducible, so it's not an argument.

I see DateTime#zone method (inherited from Date), so I think DateTime understands timezones.

@AlexWayfer
Copy link
Contributor

AlexWayfer commented Aug 21, 2018

Also, FYI, ISO 8601 doesn't contain time zones. It contains offset, it's a different thing. Multiple timezones can have the same offset at the one point of time. Do, you're just loosing DST accounting and other things.

/cc @deivid-rodriguez

Time zones in ISO 8601 are represented as local time (with the location unspecified), as UTC, or as an offset from UTC.

If no UTC relation information is given with a time representation, the time is assumed to be in local time. While it may be safe to assume local time when communicating in the same time zone, it is ambiguous when used in communicating across different time zones. Even within a single geographic time zone, some local times will be ambiguous if the region observes daylight saving time. It is usually preferable to indicate a time zone (zone designator) using the standard's notation.

https://en.wikipedia.org/wiki/ISO_8601

AlexWayfer added a commit to AlexWayfer/rubocop that referenced this issue Aug 27, 2018
1. It doesn't resolve original described problem: rubocop#1952 (comment)
2. `Time#to_s` is not ISO6801 compliant by default: rubocop#1952 (comment)
3. `Time.parse` works wrong with timezones: rubocop#1952 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests