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

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 via http://www.ruby-forum.com/.

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 via http://www.ruby-forum.com/.

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

···

Am 09.12.2012 15:07, schrieb Robert Buck:

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.

--
<https://github.com/stomar/&gt;

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 via http://www.ruby-forum.com/\.

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

···

On Monday, 10 December 2012 at 2:10 AM, Robert Buck wrote:

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 via http://www.ruby-forum.com/\.

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 via http://www.ruby-forum.com/.

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

···

--
Posted via http://www.ruby-forum.com/\.

(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.

···

On Monday, 10 December 2012 at 5:01 PM, Arlen Cuss wrote:

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

On Monday, 10 December 2012 at 2:10 AM, Robert Buck wrote:

> 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 via http://www.ruby-forum.com/\.

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.

···

On 11 December 2012 11:13, Robert Buck <lists@ruby-forum.com> wrote:

--
  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