In "Rails Recipes" book there is a recipe how to deal with time zones. However standard RoR TimeZone class doesn''t handle Daylight Saving Time. It is rather impossible to use a such solution in a commercial app. So the recipe suggests to install TZInfo gem with TimeZone class correctly handling DST (you need also tzinfo_timezone plugin). The TZInfo gem is pure ruby code and is quite slow. In fact the gem functionality is already implemented in libc (they are based on the same Olson time zone database) and Ruby Time class uses libc functions. Assuming that you keep date/time in DB as UTC and default_timezone is set to :utc (most reasonable solution), you can implement time zone conversions something like this: # to convert posted date/time to UTC and save to DB def user2utc(t) ENV["TZ"] = current_user.time_zone_name res = Time.local(t.year, t.month, t.day, t.hour, t.min, t.sec).utc ENV["TZ"] = "UTC" res end # to display date/time in a view def utc2user(t) ENV["TZ"] = current_user.time_zone_name res = t.getlocal ENV["TZ"] = "UTC" res end Above seems to work on Linux/Ubuntu and should be faster than TZInfo gem based solution. So why people try to implement their own time zone classes? Did I missed something? -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk -~----------~----~----~----~------~----~------~--~---
Grzegorz Daniluk wrote:> In "Rails Recipes" book there is a recipe how to deal with time zones. > However standard RoR TimeZone class doesn''t handle Daylight Saving Time. > It is rather impossible to use a such solution in a commercial app. So > the recipe suggests to install TZInfo gem with TimeZone class correctly > handling DST (you need also tzinfo_timezone plugin). > > The TZInfo gem is pure ruby code and is quite slow. In fact the gem > functionality is already implemented in libc (they are based on the same > Olson time zone database) and Ruby Time class uses libc functions.My tests show that TZInfo (version 0.3.1 on Ruby 1.8.5) is about 9 times slower than libc converting from UTC to local and about 7 times slower converting from local to UTC: tz = ''America/New_York'' t = Time.now.utc n = 100000 Benchmark.bm do |bm| bm.report(''tzinfo u->l:'') { n.times { TZInfo::Timezone.get(tz).utc_to_local(t) } } bm.report(''libc u->l:'') { n.times { utc2user(tz, t) } } bm.report(''tzinfo l->u:'') { n.times { TZInfo::Timezone.get(tz).local_to_utc(t) } } bm.report(''libc l->u:'') { n.times { user2utc(tz, t) } } end user system total real tzinfo u->l: 7.880000 0.000000 7.880000 ( 7.888051) libc u->l: 0.870000 0.000000 0.870000 ( 0.868656) tzinfo l->u: 10.110000 0.000000 10.110000 ( 10.116807) libc l->u: 1.500000 0.000000 1.500000 ( 1.504355)> Above seems to work on Linux/Ubuntu and should be faster than TZInfo gem > based solution. So why people try to implement their own time zone > classes? Did I missed something?The reason I started developing TZInfo was that I needed a solution that would work on both Windows and Linux. Setting the TZ environment variable doesn''t work on Windows (see http://article.gmane.org/gmane.comp.lang.ruby.rails/75790). Even if ENV[''TZ''] could be made to work, Windows only stores timezone transition information for the current year and doesn''t use compatible zone identifiers. For my purposes (use within Rails), the performance of TZInfo is good enough. I probably do at most ~100 UTC to local conversions per page (and usually far less). I have though been able to make some significant performance gains in the year since the first release and I hope to continue to make improvements in this area. Another area TZInfo improves upon using the TZ environment variable is in its handling of invalid and ambiguous local times (i.e. during the transitions to and from daylight savings). Time.local always returns a time regardless of whether it was invalid or ambiguous. TZInfo reports invalid times and allows the ambiguity to be resolved by specifying whether to use the DST or non-DST time or running a block to do the selection. In the example below 01:30 on 26-Mar-2006 shouldn''t exist as it occurs during the transition from GMT to BST. 01:30 on 29-Oct-2006 refers to both 02:30 and 01:30 UTC as it occurs during the transition from BST to GMT: irb(main):001:0> require_gem ''tzinfo'' irb(main):002:0> tz = TZInfo::Timezone.get(''Europe/London'') irb(main):003:0> ENV[''TZ''] = ''Europe/London'' irb(main):004:0> tz.current_period.local_start.to_s => "2006-03-26T02:00:00Z" irb(main):005:0> tz.local_to_utc(Time.utc(2006,3,26,1,30,0)) TZInfo::PeriodNotFound: TZInfo::PeriodNotFound from ./lib/tzinfo/timezone.rb:338:in `period_for_local'' irb(main):006:0> Time.local(2006,10,29,1,30,0).utc => Sun Oct 29 01:30:00 UTC 2006 irb(main):007:0> tz.local_to_utc(Time.utc(2006,10,29,1,30,0)) TZInfo::AmbiguousTime: Time: Sun Oct 29 01:30:00 UTC 2006 is an ambiguous local time. from ./lib/tzinfo/timezone.rb:363:in `period_for_local'' irb(main):008:0> Time.local(2006,10,29,1,30,0).utc => Sun Oct 29 01:30:00 UTC 2006 -- Philip Ross http://tzinfo.rubyforge.org/ -- DST-aware timezone library for Ruby --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk -~----------~----~----~----~------~----~------~--~---
> My tests show that TZInfo (version 0.3.1 on Ruby 1.8.5) is about 9 times > slower than libc converting from UTC to local and about 7 times slower > converting from local to UTC:Good result taking into account Ruby interpreter speed.> The reason I started developing TZInfo was that I needed a solution that > would work on both Windows and Linux. Setting the TZ environment > variable doesn''t work on WindowsI see, thanks to your library time zone conversion are fully portable. Thanks for extensive answer, GD -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk -~----------~----~----~----~------~----~------~--~---
OK. If someone uses only Linux and doesn''t need to handle exceptions for these two special hours per year then following code might be useful for him. This is very simple extension, two methods, to Ruby Time class to convert time objects between different time zones. Set your TZ evn variable to "UTC" and RoR default_timezone to :utc. Then: 1. Load new methods require("gdc_tzconv") 2. timeob.utc_to_local("Europe/Warsaw") to convert a datetime object loaded as UTC from DB to a user''s time zone. 3. timeob.local_to_utc("Europe/Warsaw") to convert a posted datetime (created as UTC time by RoR) to a real UTC time value with assumption that the original time is in a user''s local time. file extconf.rb: require ''mkmf'' create_makefile("gdc_tzconv") file gdc_tzconv.c: #include <string.h> #include <time.h> #include "ruby.h" extern VALUE rb_cTime; struct time_object { struct timeval tv; struct tm tm; int gmt; int tm_got; }; static void time_modify(time) VALUE time; { rb_check_frozen(time); if (!OBJ_TAINTED(time) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can''t modify Time"); } static VALUE gdc_tzconv_utc_to_local(VALUE self, VALUE dest_tz_name) { struct time_object *tobj; struct tm *tm_tmp; time_t t; ruby_setenv("TZ", StringValuePtr(dest_tz_name)); tzset(); Data_Get_Struct(self, struct time_object, tobj); time_modify(self); t = tobj->tv.tv_sec; tm_tmp = localtime(&t); if (!tm_tmp) { rb_raise(rb_eArgError, "localtime error"); } tobj->tm = *tm_tmp; tobj->tm_got = 1; tobj->gmt = 0; ruby_setenv("TZ", "GMT"); return self; } static VALUE gdc_tzconv_local_to_utc(VALUE self, VALUE dest_tz_name) { struct time_object *tobj; struct tm *tm_tmp, tm; time_t t; int is_dst; ruby_setenv("TZ", StringValuePtr(dest_tz_name)); tzset(); Data_Get_Struct(self, struct time_object, tobj); time_modify(self); gmtime_r(&tobj->tv.tv_sec, &tm); /* make mktime figure out whether DST is in effect */ tm.tm_isdst = -1; t = mktime(&tm); tm_tmp = gmtime(&t); if (!tm_tmp) { rb_raise(rb_eArgError, "gmtime error"); } tobj->tm = *tm_tmp; tobj->tm_got = 1; tobj->gmt = 1; ruby_setenv("TZ", "GMT"); return self; } void Init_gdc_tzconv() { rb_define_method(rb_cTime, "utc_to_local", gdc_tzconv_utc_to_local, 1); rb_define_method(rb_cTime, "local_to_utc", gdc_tzconv_local_to_utc, 1); } -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk -~----------~----~----~----~------~----~------~--~---