From 2329cb8e1e08c52ae280c7c3e5516b0a8b54378a Mon Sep 17 00:00:00 2001 From: lukemelia Date: Sat, 6 Jan 2007 10:06:33 +0000 Subject: [PATCH] Introduced user preference for time zone (migration, model and prefs forms) Made all dates and times stored in UTC in the database and presented according to the User's preferred time zone. Cleaned up old unused preference methods in UserController Restored keyboard shortcuts for date field interaction Aliased preference in User model to prefs for brevity Don't forget to rake db:migrate for this update! All tests pass, but there were a lot of little changes involved in this feature. Please help me test it thoroughly if you're using the trunk. Fixes #349 git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@392 a4c988fc-2ded-0310-b66e-134b36920a42 --- tracks/app/controllers/application.rb | 7 +- tracks/app/controllers/feed_controller.rb | 6 +- tracks/app/controllers/todo_controller.rb | 12 +- tracks/app/controllers/user_controller.rb | 22 -- tracks/app/helpers/application_helper.rb | 36 ++-- tracks/app/helpers/todo_helper.rb | 23 +-- tracks/app/models/preference.rb | 5 +- tracks/app/models/todo.rb | 8 +- tracks/app/models/user.rb | 4 +- tracks/app/views/context/show.rhtml | 2 +- tracks/app/views/layouts/standard.rhtml | 2 +- tracks/app/views/mobile/_mobile_edit.rhtml | 2 +- tracks/app/views/preferences/edit.rhtml | 6 +- tracks/app/views/preferences/index.rhtml | 1 + tracks/app/views/sidebar/sidebar.rhtml | 6 +- tracks/app/views/todo/toggle_check.rjs | 2 +- ...pref_to_show_hidden_projects_in_sidebar.rb | 2 - .../migrate/021_add_time_zone_preference.rb | 11 + tracks/db/schema.rb | 193 +++++++++--------- tracks/lib/todo_list.rb | 2 +- tracks/public/javascripts/calendar-setup.js | 5 +- tracks/test/fixtures/notes.yml | 6 +- tracks/test/fixtures/preferences.yml | 2 + tracks/test/fixtures/todos.yml | 8 +- .../functional/project_controller_test.rb | 2 +- .../test/functional/todo_controller_test.rb | 4 +- tracks/test/unit/preference_test.rb | 18 ++ tracks/test/unit/project_test.rb | 2 +- tracks/test/unit/todo_test.rb | 4 +- tracks/test/unit/user_test.rb | 6 +- 30 files changed, 218 insertions(+), 191 deletions(-) create mode 100644 tracks/db/migrate/021_add_time_zone_preference.rb create mode 100644 tracks/test/unit/preference_test.rb diff --git a/tracks/app/controllers/application.rb b/tracks/app/controllers/application.rb index fa7693db..946dc831 100644 --- a/tracks/app/controllers/application.rb +++ b/tracks/app/controllers/application.rb @@ -62,18 +62,17 @@ class ApplicationController < ActionController::Base wants.xml { render :text => 'An error occurred on the server.' + $! } end end - + private def get_current_user @user = User.find(session['user_id']) if session['user_id'] - @prefs = @user.preference unless @user.nil? + @prefs = @user.prefs unless @user.nil? end def parse_date_per_user_prefs( s ) return nil if s.blank? - Date.strptime(s, @user.preference.date_format) - #Chronic.parse(s).to_date + @user.prefs.tz.unadjust(Date.strptime(s, @user.prefs.date_format)).utc.to_date end def init_data_for_sidebar diff --git a/tracks/app/controllers/feed_controller.rb b/tracks/app/controllers/feed_controller.rb index 9ca503d3..ed3e197d 100644 --- a/tracks/app/controllers/feed_controller.rb +++ b/tracks/app/controllers/feed_controller.rb @@ -90,8 +90,8 @@ protected if params.key?('due') due_within = params['due'].to_i - condition_builder.add('todos.due <= ?', due_within.days.from_now) - due_within_date_s = due_within.days.from_now.strftime("%Y-%m-%d") + condition_builder.add('todos.due <= ?', due_within.days.from_now.utc) + due_within_date_s = @user.prefs.tz.adjust(due_within.days.from_now.utc).strftime("%Y-%m-%d") @title << " due today" if (due_within == 0) @title << " due within a week" if (due_within == 6) @description << " with a due date #{due_within_date_s} or earlier" @@ -99,7 +99,7 @@ protected if params.key?('done') done_in_last = params['done'].to_i - condition_builder.add('todos.completed_at >= ?', done_in_last.days.ago) + condition_builder.add('todos.completed_at >= ?', done_in_last.days.ago.utc) @title << " actions completed" @description << " in the last #{done_in_last.to_s} days" end diff --git a/tracks/app/controllers/todo_controller.rb b/tracks/app/controllers/todo_controller.rb index d1c0dcf8..af6eecd1 100644 --- a/tracks/app/controllers/todo_controller.rb +++ b/tracks/app/controllers/todo_controller.rb @@ -16,7 +16,7 @@ class TodoController < ApplicationController # If you've set no_completed to zero, the completed items box # isn't shown on the home page - max_completed = @user.preference.show_number_completed + max_completed = @user.prefs.show_number_completed @done = @user.completed_todos.find(:all, :limit => max_completed) unless max_completed == 0 @contexts_to_show = @contexts.reject {|x| x.hide? } @@ -60,7 +60,7 @@ class TodoController < ApplicationController if @item.due? @date = parse_date_per_user_prefs(p['todo']['due']) - @item.due = @date.to_s(:db) + @item.due = @date else @item.due = "" end @@ -206,15 +206,15 @@ class TodoController < ApplicationController def completed @page_title = "TRACKS::Completed tasks" @done = @user.completed_todos - @done_today = @done.completed_within 1.day.ago - @done_this_week = @done.completed_within 1.week.ago - @done_this_month = @done.completed_within 4.week.ago + @done_today = @done.completed_within 1.day.ago.utc + @done_this_week = @done.completed_within 1.week.ago.utc + @done_this_month = @done.completed_within 4.week.ago.utc end def completed_archive @page_title = "TRACKS::Archived completed tasks" @done = @user.completed_todos - @done_archive = @done.completed_more_than 28.day.ago + @done_archive = @done.completed_more_than 28.day.ago.utc end def tickler diff --git a/tracks/app/controllers/user_controller.rb b/tracks/app/controllers/user_controller.rb index dc9bd57e..0a034297 100644 --- a/tracks/app/controllers/user_controller.rb +++ b/tracks/app/controllers/user_controller.rb @@ -39,28 +39,6 @@ class UserController < ApplicationController end end - def preferences - @page_title = "TRACKS::Preferences" - @prefs = @user.preference - end - - def edit_preferences - @page_title = "TRACKS::Edit Preferences" - @prefs = @user.preference - - render :action => "preference_edit_form", :object => @prefs - end - - def update_preferences - user_success = @user.update_attributes(params['user']) - prefs_success = @user.preference.update_attributes(params['prefs']) - if user_success && prefs_success - redirect_to :action => 'preferences' - else - render :action => 'edit_preferences' - end - end - def change_password @page_title = "TRACKS::Change password" end diff --git a/tracks/app/helpers/application_helper.rb b/tracks/app/helpers/application_helper.rb index 400f1a4a..6d7fbda8 100644 --- a/tracks/app/helpers/application_helper.rb +++ b/tracks/app/helpers/application_helper.rb @@ -6,12 +6,17 @@ module ApplicationHelper # def format_date(date) if date - date_format = @user.preference.date_format - formatted_date = date.strftime("#{date_format}") + date_format = @user.prefs.date_format + formatted_date = @user.prefs.tz.adjust(date).strftime("#{date_format}") else formatted_date = '' end end + + def user_time + @user.prefs.tz.adjust(Time.now.utc) + end + # Uses RedCloth to transform text using either Textile or Markdown # Need to require redcloth above @@ -39,6 +44,11 @@ module ApplicationHelper "#{name || url}" end + def days_from_today(date) + today = Time.now.utc.to_date + @user.prefs.tz.adjust(date).to_date - @user.prefs.tz.adjust(today).to_date + end + # Check due date in comparison to today's date # Flag up date appropriately with a 'traffic light' colour code # @@ -47,27 +57,26 @@ module ApplicationHelper return "" end - @now = Date.today - @days = due-@now + days = days_from_today(due) - case @days + case days # overdue or due very soon! sound the alarm! when -1000..-1 - "Overdue by " + (@days * -1).to_s + " days " + "Overdue by " + (days * -1).to_s + " days " when 0 "Due Today " when 1 "Due Tomorrow " # due 2-7 days away when 2..7 - if @user.preference.due_style == "1" + if @user.prefs.due_style == "1" "Due on " + due.strftime("%A") + " " else - "Due in " + @days.to_s + " days " + "Due in " + days.to_s + " days " end # more than a week away - relax else - "Due in " + @days.to_s + " days " + "Due in " + days.to_s + " days " end end @@ -80,10 +89,9 @@ module ApplicationHelper return "" end - @now = Date.today - @days = due-@now + days = days_from_today(due) - case @days + case days # overdue or due very soon! sound the alarm! when -1000..-1 "" + format_date(due) +"" @@ -130,13 +138,13 @@ module ApplicationHelper def item_link_to_context(item) descriptor = "[C]" - descriptor = "[#{item.context.name}]" if (@user.preference.verbose_action_descriptors) + descriptor = "[#{item.context.name}]" if (@user.prefs.verbose_action_descriptors) link_to_context( item.context, descriptor ) end def item_link_to_project(item) descriptor = "[P]" - descriptor = "[#{item.project.name}]" if (@user.preference.verbose_action_descriptors) + descriptor = "[#{item.project.name}]" if (@user.prefs.verbose_action_descriptors) link_to_project( item.project, descriptor ) end diff --git a/tracks/app/helpers/todo_helper.rb b/tracks/app/helpers/todo_helper.rb index fb9eb102..c2c97a9f 100644 --- a/tracks/app/helpers/todo_helper.rb +++ b/tracks/app/helpers/todo_helper.rb @@ -42,11 +42,11 @@ module TodoHelper def staleness_class(item) if item.due || item.completed? return "" - elsif item.created_at < (@user.preference.staleness_starts * 3).days.ago + elsif item.created_at < (@user.prefs.staleness_starts * 3).days.ago.utc return " stale_l3" - elsif item.created_at < (@user.preference.staleness_starts * 2).days.ago + elsif item.created_at < (@user.prefs.staleness_starts * 2).days.ago.utc return " stale_l2" - elsif item.created_at < (@user.preference.staleness_starts).days.ago + elsif item.created_at < (@user.prefs.staleness_starts).days.ago.utc return " stale_l1" else return "" @@ -61,33 +61,32 @@ module TodoHelper return "" end - @now = Date.today - @days = due-@now + days = days_from_today(due) - case @days + case days # overdue or due very soon! sound the alarm! when -1000..-1 - "Shown on " + (@days * -1).to_s + " days " + "Shown on " + (days * -1).to_s + " days " when 0 "Show Today " when 1 "Show Tomorrow " # due 2-7 days away when 2..7 - if @user.preference.due_style == 1 + if @user.prefs.due_style == 1 "Show on " + due.strftime("%A") + " " else - "Show in " + @days.to_s + " days " + "Show in " + days.to_s + " days " end # more than a week away - relax else - "Show in " + @days.to_s + " days " + "Show in " + days.to_s + " days " end end def calendar_setup( input_field ) - date_format = @user.preference.date_format - week_starts = @user.preference.week_starts + date_format = @user.prefs.date_format + week_starts = @user.prefs.week_starts str = "Calendar.setup({ ifFormat:\"#{date_format}\"" str << ",firstDay:#{week_starts},showOthers:true,range:[2004, 2010]" str << ",step:1,inputField:\"" + input_field + "\",cache:true,align:\"TR\" })\n" diff --git a/tracks/app/models/preference.rb b/tracks/app/models/preference.rb index 316f2e22..19d9664e 100644 --- a/tracks/app/models/preference.rb +++ b/tracks/app/models/preference.rb @@ -1,6 +1,9 @@ class Preference < ActiveRecord::Base belongs_to :user - + composed_of :tz, + :class_name => 'TimeZone', + :mapping => %w(time_zone name) + def self.day_number_to_name_map { 0 => "Sunday", 1 => "Monday", diff --git a/tracks/app/models/todo.rb b/tracks/app/models/todo.rb index d02052f5..e11c3807 100644 --- a/tracks/app/models/todo.rb +++ b/tracks/app/models/todo.rb @@ -9,7 +9,7 @@ class Todo < ActiveRecord::Base state :active, :enter => Proc.new { |t| t[:show_from] = nil } state :project_hidden - state :completed, :enter => Proc.new { |t| t.completed_at = Time.now() }, :exit => Proc.new { |t| t.completed_at = nil } + state :completed, :enter => Proc.new { |t| t.completed_at = Time.now.utc }, :exit => Proc.new { |t| t.completed_at = nil } state :deferred event :defer do @@ -45,7 +45,7 @@ class Todo < ActiveRecord::Base validates_presence_of :context def validate - if deferred? && !show_from.blank? && show_from < Date.today() + if deferred? && !show_from.blank? && show_from < Time.now.utc.to_date errors.add("Show From", "must be a date in the future.") end end @@ -65,7 +65,7 @@ class Todo < ActiveRecord::Base def show_from=(date) activate! if deferred? && date.blank? - defer! if active? && !date.blank? && date > Date.today() + defer! if active? && !date.blank? && date > Time.now.utc.to_date self[:show_from] = date end @@ -78,7 +78,7 @@ class Todo < ActiveRecord::Base alias_method :original_set_initial_state, :set_initial_state def set_initial_state - if show_from && (show_from > Date.today) + if show_from && (show_from > Time.now.utc.to_date) write_attribute self.class.state_column, 'deferred' else original_set_initial_state diff --git a/tracks/app/models/user.rb b/tracks/app/models/user.rb index 3f0e5894..3ec3277e 100644 --- a/tracks/app/models/user.rb +++ b/tracks/app/models/user.rb @@ -15,7 +15,7 @@ class User < ActiveRecord::Base :conditions => [ 'state = ?', 'deferred' ], :order => 'show_from ASC, created_at DESC' do def find_and_activate_ready - find(:all, :conditions => ['show_from <= ?', Date.today ]).collect { |t| t.activate_and_save! } + find(:all, :conditions => ['show_from <= ?', Time.now.utc.to_date ]).collect { |t| t.activate_and_save! } end end has_many :completed_todos, @@ -45,6 +45,8 @@ class User < ActiveRecord::Base validates_inclusion_of :auth_type, :in => Tracks::Config.auth_schemes, :message=>"not a valid authentication type" validates_presence_of :open_id_url, :if => Proc.new{|user| user.auth_type == 'open_id'} + alias_method :prefs, :preference + def self.authenticate(login, pass) candidate = find(:first, :conditions => ["login = ?", login]) return nil if candidate.nil? diff --git a/tracks/app/views/context/show.rhtml b/tracks/app/views/context/show.rhtml index f4aa9aad..e0a3f013 100644 --- a/tracks/app/views/context/show.rhtml +++ b/tracks/app/views/context/show.rhtml @@ -1,6 +1,6 @@
<%= render :partial => "context/context", :locals => { :context => @context, :collapsible => false } %> -<%= render :partial => "todo/completed", :locals => { :done => @done, :collapsible => false, :append_descriptor => "in this context (last #{@user.preference.show_number_completed})" } %> +<%= render :partial => "todo/completed", :locals => { :done => @done, :collapsible => false, :append_descriptor => "in this context (last #{@user.prefs.show_number_completed})" } %>
diff --git a/tracks/app/views/layouts/standard.rhtml b/tracks/app/views/layouts/standard.rhtml index 54310988..370ce912 100644 --- a/tracks/app/views/layouts/standard.rhtml +++ b/tracks/app/views/layouts/standard.rhtml @@ -27,7 +27,7 @@ <% if @count %> <%= @count %> <% end %> - <%= Time.now.strftime("%A, %d %B %Y") %> <%= image_tag("spinner.gif", :size => "16X16", :border => 0, :id => 'busy', :style => 'display:none' ) %> + <%= user_time.strftime("%A, %d %B %Y") %> <%= image_tag("spinner.gif", :size => "16X16", :border => 0, :id => 'busy', :style => 'display:none' ) %>