Forum: Ruby Time.new(2001, 12, 3).to_i returns wrong value

Posted by Robert Buck (rbuck)
on 2012-12-09 15:07
I am doing something that not many do, I am writing a database driver
for our database, and an active record adapter.

I am having difficulty with Date and Time. Namely, the Ruby doc clearly
states that it represents times internally in UTC seconds since the
common epoch. However, if you look at the Ruby C code clearly this is
not the case, or at least, all operations are performed relative to the
default timezone. For instance:

Time.new(2001, 12, 3).to_i

According to international standards this OUGHT to return 1007337600,
but no, it returns 1007355600, despite the Ruby documentation stating
that the to_i method is supposed to return seconds since the Epoch. What
was left purposefully vague in the doc was that it returns the seconds
since the Epoch RELATIVE TO the timezone. Very strange!!! So I have to
resort to secondary calculations to account for this behavior in Ruby:

true_seconds = time.to_i + time.utc_offset

Comments, questions?

Now here is the sticking point... On the return path FROM the database
when given the good and proper time of 1007337600, how do we convert
back to a Time properly? Note that Time.at adjusts for timezone, so all
values will be off by the timezone offset. I could daisy chain the
construction of multiple Time objects to coerce the values properly, but
it should be simpler than that.

Thoughts?
Posted by Robert Buck (rbuck)
on 2012-12-09 16:10
I basically subtracted the timezone offset for Time.new from my C code
and that fixed it all regardless of what default timezone is set.
Posted by Arlen Cuss (unnali)
on 2012-12-10 07:01
(Received via mailing list)
Time internally stores the value in UTC, as does #to_i properly return 
the seconds since the epoch; the issue here is in your construction of 
it: the default is to use your local timezone.

I'm in +11, hence:

> > Time.new(2001, 12, 3)
=> 2001-12-03 00:00:00 +1100
> > Time.new(2001, 12, 3).to_i
>

=> 1007298000
> > Time.new(2001, 12, 3, 0, 0, 0, 0)
>

=> 2001-12-03 00:00:00 +0000
> > Time.new(2001, 12, 3, 0, 0, 0, 0).to_i
>

=> 1007337600
Posted by Arlen Cuss (unnali)
on 2012-12-10 07:02
(Received via mailing list)
(In other words: the value is correct, and Time.at will provide you with 
the correct time (note that Time.at(x).to_i == x, whatever your local 
timezone), so I suggest you just ensure you're putting the right data 
in, and pay close attention to the pretty-printed Time values' 
timezones.
Posted by unknown (Guest)
on 2012-12-10 13:50
(Received via mailing list)
Am 09.12.2012 15:07, schrieb Robert Buck:
> I am doing something that not many do, I am writing a database driver
> for our database, and an active record adapter.
>
> I am having difficulty with Date and Time. Namely, the Ruby doc clearly
> states that it represents times internally in UTC seconds since the
> common epoch. However, if you look at the Ruby C code clearly this is
> not the case, or at least, all operations are performed relative to the
> default timezone. For instance:
>
> Time.new(2001, 12, 3).to_i

Time.new uses the system time zone by default:

[10] pry(main)> time = Time.new(2001, 12, 3)
=> 2001-12-03 00:00:00 +0600
[11] pry(main)> time.to_i
=> 1007316000
[12] pry(main)> time = Time.new(2001, 12, 3, 0, 0, 0, '+00:00')
=> 2001-12-03 00:00:00 +0000
[13] pry(main)> time.to_i
=> 1007337600

Or you can use Time.utc:

[26] pry(main)> time = Time.utc(2001, 12, 3)
=> 2001-12-03 00:00:00 UTC
[27] pry(main)> time.to_i
=> 1007337600
Posted by Robert Buck (rbuck)
on 2012-12-11 02:13
Yes, the big problem here is that, since what I am writing is a database
driver, users can throw anything at me. So I have no control over how
the Time/Date objects are created. But at the database interface I need
to normalize, for both inserts and queries. Since the internal database
API assumes those date/times that are passed as millis since the epoch
are UTC, I do that conversion explicitly using offsets. For string
representations during inserts I provide my own quote_date function in
the AR Adapter. On the return route I have resorted to always providing
Date/Time in local timezone, applying the opposite adjustment described
above.
Posted by Matthew Kerwin (mattyk)
on 2012-12-11 02:52
(Received via mailing list)
On 11 December 2012 11:13, Robert Buck <lists@ruby-forum.com> wrote:

> Yes, the big problem here is that, since what I am writing is a database
> driver, users can throw anything at me. So I have no control over how
> the Time/Date objects are created.


If you're writing the driver, you can define the API.  If you specify
clearly what is allowed and how it will be interpreted, there's not 
(much
of) a problem.


> But at the database interface I need
> to normalize, for both inserts and queries. Since the internal database
> API assumes those date/times that are passed as millis since the epoch
> are UTC, I do that conversion explicitly using offsets.


I'm still having trouble understanding "since the internal database API
assumes those date/times that are passed as millis since the epoch are 
UTC
..."  It's my understand that [milli]seconds-since-the-epoch is a
timezone-agnostic value, because there was only one epoch, everywhere in
the world, at the same instant, irrespective of what was showing on your
wall-clock at that time.

The time that was 1_098_293_801 seconds after the epoch may be written 
as
"2004-10-20 17:36:41 UTC" or "2004-10-21 03:36:41 +1000" or whatever, 
but
it's still the same time.

Thus, if the user provides you with an integer, no interpretation is
required on your part.  Store it as an integer.  And in Ruby you can use
Time.at(seconds_since_epoch) , or if you really want to see it as a UTC
string, Time.at(seconds_since_epoch).utc.strftime('%FT%TZ') or whatever.

Conversely, if you allow the user to provide you with a string, like
'YYYY-MM-DD' you have to have some explicit rules about how that will be
converted to either a timezone-aware Time object, or the equivalent
timezone-agnostic seconds-since-the-epoch value.


> For string
> representations during inserts I provide my own quote_date function in
> the AR Adapter.


Exactly.


> On the return route I have resorted to always providing
> Date/Time in local timezone, applying the opposite adjustment described
> above.


When returning values from the database, assuming they're stored either 
as
timezone-agnostic seconds-since-the-epoch (INTEGER) values, or as
timezone-aware DATETIME fields, you can reconstruct a Time object 
precisely
(using either Time::at , or Time::mktime in the long form).  There's no
"resorting" to anything.

--
  Matthew Kerwin, B.Sc (CompSci) (Hons)
  http://matthew.kerwin.net.au/
  ABN: 59-013-727-651

  "You'll never find a programming language that frees
  you from the burden of clarifying your ideas." - xkcd
Posted by Brian Candler (candlerb)
on 2012-12-11 17:26
Robert Buck wrote in post #1088400:
> Time.new(2001, 12, 3).to_i
>
> According to international standards this OUGHT to return 1007337600,
> but no, it returns 1007355600, despite the Ruby documentation stating
> that the to_i method is supposed to return seconds since the Epoch.

It does construct the epoch time, but in this case interprets the Y/M/D 
you are passing in as for midnight in the local timezone.

Just use the method which does what you want, which in this case is 
Time.gm

>> ENV['TZ']="Etc/GMT-5"
=> "Etc/GMT-5"
>> a = Time.mktime(2001,12,3)
=> Mon Dec 03 00:00:00 +0500 2001
>> a.to_i
=> 1007319600
>> b = Time.gm(2001,12,3)
=> Mon Dec 03 00:00:00 UTC 2001
>> b.to_i
=> 1007337600

[tested with ruby 1.8]
Posted by Brian Candler (candlerb)
on 2012-12-11 17:27
Brian Candler wrote in post #1088693:
> Just use the method which does what you want, which in this case is
> Time.gm

... or the better-named Time.utc
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.