How do I simulate an SQL `timestamp` in plain old Ruby? - ruby

I am enforcing the output of a datetime as Zulu-Time by doing the following
object.updated_at.utc.iso8601 # => "2013-05-12T10:47:01Z"
This works fine for a datetime but when the object is persisting to a database as a timestamp instead I get
"Sun, 12 May 2013 10:47:01 UTC +00:00"
Which is not a Zulu Time string.
I can fix this by
object.updated_at.to_time.utc.iso8601
but I am now trying to unit test this fix, and I can't reproduce the timestamp format to test it.
How do I simulate an SQL timestamp such that .utc.iso8601 returns "Sun, 12 May 2013 10:47:01 UTC +00:00", but without wrapping it in a whole mess of ActiveRecord etc?

Turned out my problem was me not wearing my glasses and misreading my own test code. I'd close the question except there is no 'closing the question because I forgot my glasses' option.

Related

PST/PDT in DateTime.parse in Ruby

I want to be able to parse a date string which might be in PST or PDT
according to daylight savings. This means that half a year the date will
be PST and the other half the date will be PDT.
This is my code now:
DateTime.parse('2016-02-21 10:00:02 PST/PDT')
This will only parse the date as PST (GMT-8).
How can I parse a date+time in PST/PDT automatically?
Thanks!
Just a note:
DateTime.parse('2016-02-21 10:00:02 PST/PDT') will always parse to PST (Standard Time), just like DateTime.parse('2016-02-21 10:00:02 PDT') will always parse to PDT (Daylight time). This is because DateTime & Time libraries are expecting that the timezone is explicit, rather than 'PST/PDT' which is saying 'I could be x or y'. If it was smarter it could work out that both of these where in the same zone and that they were daylight savings equivalents, but sadly not at this moment.
A few options:
1) Use a timezone gem to translate the time into local zones
2) Manually put in PST or PDT depending on year
I tend to store all date/times in UTC and translate as I need them.
I use the TZInfo gem to display/calculate based on local time. It uses timezones from here
recorded_time = Time.now.getutc
tz = TZInfo::Timezone.get('US/Pacific')
local_created_at = tz.utc_to_local(recorded_time)
Now this will not solve your issue if you have data already stored in DateTime already or if your inbound data is already marked up this way. If it is, I would suggest you parse it based on date. You can create a look up table using this data: http://www.timeanddate.com/time/zone/usa/los-angeles
EDIT: Just realised the TZinfo is able to solve this for you:
tz = TZInfo::Timezone.get('US/Pacific')
=> #<TZInfo::DataTimezone: US/Pacific>
tz.local_to_utc(Time.parse('2016-07-21 10:00:02 PST/PDT'))
=> 2016-07-21 17:00:02 UTC
tz.local_to_utc(Time.parse('2016-02-21 10:00:02 PST/PDT'))
=> 2016-02-21 18:00:02 UTC

Ruby, get difference between GMT of timezone

I'm going to get difference between GMT (for example: +09:00) in Ruby, using TZInfo gem. I'm reading documentation and I can't find the solution. I tried:
tz = TZInfo::Timezone.get('Asia/Shanghai')
puts tz.strftime('%:z')
But it returns +00:00, that's not correct, why? It's looks like it still using London time.
This should do it, but as someone pointed out, you can't just use the location, you need to check for daylight savings etc.
tz = TZInfo::Timezone.get('Asia/Shanghai')
puts sprintf("%02d:%02d", *tz.current_period.offset.utc_offset.divmod(3600))
A time zone by itself does not have the notion of a time difference because the time difference may change during the period within the year (some time zones have summer time). You need to assign particular date time in order to get a meaningful time difference.

Validate dates before 1970-01-01

How can I validate date before 1970-01-01 (like 1900-01-01)?
When I try to use CDateVaildator it ends with CTimestamp::getTimestamp() (I found it with help of debugger)
return #mktime($hr,$min,$sec,$mon,$day,$year)
where $hr=0, $min=0, $sec=0, $mon=1, $dat=1, $year=1900 which obviously returns false and this fails whole validation.
CTimestamp::getTimestamp() will generate a timestamp for any dates from 1901 (not 1900!) up to 2038. For dates between 1901 and 1970 it generates a negative number. Just tested it, and the earliest date I can get to work is 14th December 1901. Anything before this throws an error. Not sure how to deal with dates outside this range!
In Yii there is an error described here. The solution is presented there but for some reason it's not included in Yii 1.1.14 although it's marked as committed to trunk.
To make long story short - the fix tries to build timestamp only when there is a property in rules defined.
To use it in own project you have to extend CDateValidator and CDateTimeParser.

Millisecond resolution of DateTime in Ruby

I have a string like 2012-01-01T01:02:03.456 that I am storing in a Postgres database TIMESTAMP using ActiveRecord.
Unfortunately, Ruby seems to chop off the milliseconds:
ruby-1.9.3-rc1 :078 > '2012-12-31T01:01:01.232323+3'.to_datetime
=> Mon, 31 Dec 2012 01:01:01 +0300
Postgrs supports microsecond resolution. How can I get my timestamp to be saved accordingly? I need at least millisecond resolution.
(PS Yes I could hack in a milliseconds integer column in postgres; that kind of defeats the whole purpose of ActiveRecord.)
UPDATE:
The very helpful responses showed that Ruby's DateTime is not chopping off milliseconds; using #to_f shows it. But, doing:
m.happened_at = '2012-01-01T00:00:00.32323'.to_datetime
m.save!
m.reload
m.happened_at.to_f
Does drop the milliseconds.
Now, the interesting thing is that created_at does show milliseconds, both in Rails and Postgres. But other timestamps fields (like happened_at above) don't. (Perhaps Rails uses a NOW() function for created_at as opposed to passing in a DateTime).
Which leads to my ultimate question:
How can I get ActiveRecord to preserve millisecond resolution on timestamp fields?
ActiveRecord should preserve the full precision from the database, you're just not looking at it properly. Use strftime and the %N format to see the fractional seconds. For example, psql says this:
=> select created_at from models where id = 1;
created_at
----------------------------
2012-02-07 07:36:20.949641
(1 row)
and ActiveRecord says this:
> Model.find(1).created_at.strftime('%Y-%m-%d %H:%M:%S.%N')
=> "2012-02-07 07:36:20.949641000"
So everything is there, you just need to know how to see it.
Also note that ActiveRecord will probably give you ActiveSupport::TimeWithZone objects rather than DateTime objects but DateTime preserves everything too:
> '2012-12-31T01:01:01.232323+3'.to_datetime.strftime('%Y-%m-%d %H:%M:%S.%N')
=> "2012-12-31 01:01:01.232323000"
Have a look at connection_adapters/column.rb in the ActiveRecord source and check what the string_to_time method does. Your string would go down the fallback_string_to_time path and that preserves fractional seconds as near as I can tell. Something strange could be going on elsewhere, I wouldn't be surprised given the strange things I've seen in the Rails source, especially the database side of things. I'd try converting the strings to objects by hand so that ActiveRecord will keeps its hands off them.
Changing m.happened_at = '2012-01-01T00:00:00.32323'.to_datetime in the code above to m.happened_at = '2012-01-01T00:00:00.32323' solves the problem, though I have no idea why.
I ended up here when I was suffering from using the RVM provided binary Ruby 2.0.0-p247 on OS X (Mavericks) which was causing rounding to whole values of seconds when retrieving times from Postgres. Rebuilding Ruby myself (rvm reinstall 2.0.0 --disable-binary) solved the issue for me.
See https://github.com/wayneeseguin/rvm/issues/2189 which I found via https://github.com/rails/rails/issues/12422.
I recognise that this is not THE answer to this issue but I hope this note might help someone struggling with it.
to_datetime does not destroy millisecond resolution of data - it's simply hidden because DateTime#to_s doesn't display it.
[1] pry(main)> '2012-12-31T01:01:01.232323+3'.to_datetime
=> Mon, 31 Dec 2012 01:01:01 +0300
[2] pry(main)> '2012-12-31T01:01:01.232323+3'.to_datetime.to_f
=> 1356904861.232323
That said, I suspect that ActiveRecord is mistakenly hiding that information when persisting the data; remember that it is database-agnostic, so it takes approaches that are guaranteed to work across all of its database targets. While Postgres supposed microsecond information in timestamps, MySQL does not, so I suspect AR selects for the lowest common denominator. I couldn't be sure without getting into the guts of AR. You may need a Postgres-specific monkeypatch to enable this behavior.

Is there a full implementation for ISO-8601 date parsing for Ruby?

The Time.iso8601 method is a restricted subset of ISO-8601.
What are its limitations?
Does anyone know of a full implementation for Ruby? I'm using MRI 1.8.7.
Update
It looks like there isn't a single class that handles all of the various 8601 date and date/time combinations. However, I have managed to work around the problems by using both the Date.parse and Time.iso8601 methods. The downside is that you need to decide in code whether the input looks like a date or a date/time.
Warning : Timezone differences
Time.iso8601 and Time.parse behave differently.
>> Time.parse("2010-09-06T12:27:00.10-05:00")
=> Mon Sep 06 18:27:00 +0100 2010
>> Time.iso8601("2010-09-06T12:27:00.10-05:00")
=> Mon Sep 06 17:27:00 UTC 2010
Differences between Time.iso8601 and ISO-8601
This document touches on the differences between what is in ISO-8601 and what is supported by Ruby. The short answer is that the number of possible formats is restricted.
Yes, but unfortunately it's in Ruby 1.9.
require "date"
Date.iso8601("2010-W32-5").strftime
#=> "2010-08-13"
I don't believe there are any implementations for Ruby 1.8.7 (or at least I couldn't find any). You could either try to upgrade to Ruby 1.9, which is pretty stable as of 1.9.2. Alternatively, you could try to parse the dates yourself.
To convert an ISO8601 date into the local time zone, do this:
require "time"
dt1 = Time.parse("2010-09-06T12:27:00.10-05:00")
To convert an ISO8601 date into UTC, do this:
dt2 = Time.iso8601("2010-09-06T12:27:00.10-05:00")
If you compare the dates returned by the above queries, they will be identical (i.e. dt1 === dt2). However, accessing date components (like year, month, day, hour, etc.) will return values appropriate for the time zone (either UTC or local). The same applies to strftime.

Resources