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?

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.

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

Am 09.12.2012 15:07, schrieb Robert B.:

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

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

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.

Robert B. 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]

Brian C. 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

On 11 December 2012 11:13, Robert B. [email protected] 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 K., 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