├── .gitignore ├── .rubocop.yml ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── config └── locales │ ├── time_of_day.de.yml │ └── time_of_day.en.yml ├── lib ├── time_of_day_attr.rb └── time_of_day_attr │ ├── active_record_extension.rb │ ├── form_builder_extension.rb │ ├── railtie.rb │ ├── seconds.rb │ ├── time_format.rb │ └── time_of_day.rb ├── test ├── active_record_extension_test.rb ├── models │ └── business_hour.rb ├── schema.rb ├── test_helper.rb └── time_of_day_attr │ ├── seconds_test.rb │ └── time_of_day_test.rb └── time_of_day_attr.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | .ruby-version 2 | .bundle/ 3 | log/*.log 4 | pkg/ 5 | test/dummy/db/*.sqlite3 6 | test/dummy/log/*.log 7 | test/dummy/tmp/ 8 | test/dummy/.sass-cache 9 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/Documentation: 2 | Enabled: false 3 | 4 | Metrics/LineLength: 5 | Max: 92 6 | Exclude: 7 | - test/**/* 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Declare your gem's dependencies in time_of_day.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | # Declare any dependencies that are still in development here instead of in 9 | # your gemspec. These might include edge Rails or gems from your path or 10 | # Git. Remember to move these dependencies to your gemspec before releasing 11 | # your gem to rubygems.org. 12 | 13 | group :test do 14 | gem 'minitest' 15 | gem 'shoulda-context' 16 | end 17 | 18 | gem 'rails' 19 | gem 'rubocop' 20 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | time_of_day_attr (3.0.0) 5 | i18n (>= 0.7) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (5.2.0) 11 | actionpack (= 5.2.0) 12 | nio4r (~> 2.0) 13 | websocket-driver (>= 0.6.1) 14 | actionmailer (5.2.0) 15 | actionpack (= 5.2.0) 16 | actionview (= 5.2.0) 17 | activejob (= 5.2.0) 18 | mail (~> 2.5, >= 2.5.4) 19 | rails-dom-testing (~> 2.0) 20 | actionpack (5.2.0) 21 | actionview (= 5.2.0) 22 | activesupport (= 5.2.0) 23 | rack (~> 2.0) 24 | rack-test (>= 0.6.3) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 27 | actionview (5.2.0) 28 | activesupport (= 5.2.0) 29 | builder (~> 3.1) 30 | erubi (~> 1.4) 31 | rails-dom-testing (~> 2.0) 32 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 33 | activejob (5.2.0) 34 | activesupport (= 5.2.0) 35 | globalid (>= 0.3.6) 36 | activemodel (5.2.0) 37 | activesupport (= 5.2.0) 38 | activerecord (5.2.0) 39 | activemodel (= 5.2.0) 40 | activesupport (= 5.2.0) 41 | arel (>= 9.0) 42 | activestorage (5.2.0) 43 | actionpack (= 5.2.0) 44 | activerecord (= 5.2.0) 45 | marcel (~> 0.3.1) 46 | activesupport (5.2.0) 47 | concurrent-ruby (~> 1.0, >= 1.0.2) 48 | i18n (>= 0.7, < 2) 49 | minitest (~> 5.1) 50 | tzinfo (~> 1.1) 51 | arel (9.0.0) 52 | ast (2.4.0) 53 | builder (3.2.3) 54 | concurrent-ruby (1.0.5) 55 | crass (1.0.4) 56 | erubi (1.7.1) 57 | globalid (0.4.1) 58 | activesupport (>= 4.2.0) 59 | i18n (1.0.1) 60 | concurrent-ruby (~> 1.0) 61 | jaro_winkler (1.5.1) 62 | loofah (2.2.2) 63 | crass (~> 1.0.2) 64 | nokogiri (>= 1.5.9) 65 | mail (2.7.0) 66 | mini_mime (>= 0.1.1) 67 | marcel (0.3.2) 68 | mimemagic (~> 0.3.2) 69 | method_source (0.9.0) 70 | mimemagic (0.3.2) 71 | mini_mime (1.0.0) 72 | mini_portile2 (2.3.0) 73 | minitest (5.11.3) 74 | nio4r (2.3.1) 75 | nokogiri (1.8.4) 76 | mini_portile2 (~> 2.3.0) 77 | parallel (1.12.1) 78 | parser (2.5.1.2) 79 | ast (~> 2.4.0) 80 | powerpack (0.1.2) 81 | rack (2.0.5) 82 | rack-test (1.0.0) 83 | rack (>= 1.0, < 3) 84 | rails (5.2.0) 85 | actioncable (= 5.2.0) 86 | actionmailer (= 5.2.0) 87 | actionpack (= 5.2.0) 88 | actionview (= 5.2.0) 89 | activejob (= 5.2.0) 90 | activemodel (= 5.2.0) 91 | activerecord (= 5.2.0) 92 | activestorage (= 5.2.0) 93 | activesupport (= 5.2.0) 94 | bundler (>= 1.3.0) 95 | railties (= 5.2.0) 96 | sprockets-rails (>= 2.0.0) 97 | rails-dom-testing (2.0.3) 98 | activesupport (>= 4.2.0) 99 | nokogiri (>= 1.6) 100 | rails-html-sanitizer (1.0.4) 101 | loofah (~> 2.2, >= 2.2.2) 102 | railties (5.2.0) 103 | actionpack (= 5.2.0) 104 | activesupport (= 5.2.0) 105 | method_source 106 | rake (>= 0.8.7) 107 | thor (>= 0.18.1, < 2.0) 108 | rainbow (3.0.0) 109 | rake (12.3.1) 110 | rubocop (0.58.1) 111 | jaro_winkler (~> 1.5.1) 112 | parallel (~> 1.10) 113 | parser (>= 2.5, != 2.5.1.1) 114 | powerpack (~> 0.1) 115 | rainbow (>= 2.2.2, < 4.0) 116 | ruby-progressbar (~> 1.7) 117 | unicode-display_width (~> 1.0, >= 1.0.1) 118 | ruby-progressbar (1.9.0) 119 | shoulda-context (1.2.2) 120 | sprockets (3.7.2) 121 | concurrent-ruby (~> 1.0) 122 | rack (> 1, < 3) 123 | sprockets-rails (3.2.1) 124 | actionpack (>= 4.0) 125 | activesupport (>= 4.0) 126 | sprockets (>= 3.0.0) 127 | sqlite3 (1.3.13) 128 | thor (0.20.0) 129 | thread_safe (0.3.6) 130 | tzinfo (1.2.5) 131 | thread_safe (~> 0.1) 132 | unicode-display_width (1.4.0) 133 | websocket-driver (0.7.0) 134 | websocket-extensions (>= 0.1.0) 135 | websocket-extensions (0.1.3) 136 | 137 | PLATFORMS 138 | ruby 139 | 140 | DEPENDENCIES 141 | minitest 142 | rails 143 | rubocop 144 | shoulda-context 145 | sqlite3 146 | time_of_day_attr! 147 | 148 | BUNDLED WITH 149 | 1.16.0 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Time of day attributes for your Rails model 2 | [![Gem Version](https://badge.fury.io/rb/time_of_day_attr.png)](http://badge.fury.io/rb/time_of_day_attr) [![Code Climate](https://codeclimate.com/github/clemenst/time_of_day_attr.png)](https://codeclimate.com/github/clemenst/time_of_day_attr) [![License](https://img.shields.io/npm/l/express.svg?style=flat)](http://clemenst.mit-license.org) 3 | 4 | This ruby gem converts time of day to seconds (since midnight) and back. The value in seconds can be used for calculations and validations. 5 | 6 | ## Installation 7 | 8 | ```console 9 | gem install time_of_day_attr 10 | ``` 11 | 12 | ## Usage 13 | 14 | Define the time of day attributes: 15 | ```ruby 16 | class BusinessHour < ActiveRecord::Base 17 | time_of_day_attr :opening, :closing 18 | end 19 | ``` 20 | 21 | Converts time of day to seconds since midnight when a string was set: 22 | ```ruby 23 | business_hour = BusinessHour.new(opening: '9:00', closing: '17:00') 24 | business_hour.opening 25 | => 32400 26 | business_hour.closing 27 | => 61200 28 | ``` 29 | 30 | To convert back to time of day: 31 | ```ruby 32 | TimeOfDayAttr.l(business_hour.opening) 33 | => '9:00' 34 | TimeOfDayAttr.l(business_hour.closing) 35 | => '17:00' 36 | ``` 37 | 38 | You could also omit minutes at full hour: 39 | ```ruby 40 | TimeOfDayAttr.l(business_hour.opening, omit_minutes_at_full_hour: true) 41 | => '9' 42 | ``` 43 | 44 | ### Formats 45 | 46 | The standard formats for conversion are 'default' and 'hour'. 47 | ```yml 48 | en: 49 | time_of_day: 50 | formats: 51 | default: '%k:%M' 52 | hour: '%k' 53 | ``` 54 | 55 | You can overwrite them or use custom formats: 56 | ```yml 57 | en: 58 | time_of_day: 59 | formats: 60 | custom: '%H-%M' 61 | ``` 62 | 63 | Pass the formats you want for conversion: 64 | ```ruby 65 | class BusinessHour < ActiveRecord::Base 66 | time_of_day_attr :opening, formats: [:custom] 67 | end 68 | ``` 69 | 70 | ```ruby 71 | business_hour = BusinessHour.new(opening: '09-00') 72 | business_hour.opening 73 | => 32400 74 | TimeOfDayAttr.l(business_hour.opening, format: :custom) 75 | => '09-00' 76 | ``` 77 | 78 | ### Prepending 79 | 80 | If you want to process the converted value in your model, you can use the prepend option: 81 | 82 | ```ruby 83 | class BusinessHour < ActiveRecord::Base 84 | attr_reader :tracked_opening, :tracked_closing 85 | 86 | time_of_day_attr :opening 87 | time_of_day_attr :closing, prepend: true 88 | 89 | def opening=(value) 90 | @tracked_opening = value 91 | super(value) 92 | end 93 | 94 | def closing=(value) 95 | @tracked_closing = value 96 | super(value) 97 | end 98 | end 99 | ``` 100 | 101 | ```ruby 102 | business_hour = BusinessHour.new(opening: '9', closing: '9') 103 | business_hour.tracked_opening 104 | => '9' 105 | business_hour.tracked_closing 106 | => 32400 107 | ``` 108 | 109 | ### time of day field 110 | 111 | To get a text field with the converted value: 112 | ```erb 113 | <%= form_for(business_hour) do |f| %> 114 | <%= f.time_of_day_field(:opening) %> 115 | <% end %> 116 | ``` 117 | 118 | ## License 119 | 120 | [MIT](http://clemenst.mit-license.org) 121 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | begin 3 | require 'bundler/setup' 4 | rescue LoadError 5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 6 | end 7 | begin 8 | require 'rdoc/task' 9 | rescue LoadError 10 | require 'rdoc/rdoc' 11 | require 'rake/rdoctask' 12 | RDoc::Task = Rake::RDocTask 13 | end 14 | 15 | RDoc::Task.new(:rdoc) do |rdoc| 16 | rdoc.rdoc_dir = 'rdoc' 17 | rdoc.title = 'TimeOfDay' 18 | rdoc.options << '--line-numbers' 19 | rdoc.rdoc_files.include('README.rdoc') 20 | rdoc.rdoc_files.include('lib/**/*.rb') 21 | end 22 | 23 | Bundler::GemHelper.install_tasks 24 | 25 | require 'rake/testtask' 26 | 27 | Rake::TestTask.new(:test) do |t| 28 | t.libs << 'lib' 29 | t.libs << 'test' 30 | t.pattern = 'test/**/*_test.rb' 31 | t.verbose = false 32 | end 33 | 34 | task default: :test 35 | -------------------------------------------------------------------------------- /config/locales/time_of_day.de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | time_of_day: 3 | formats: 4 | default: '%k:%M' 5 | hour: '%k' 6 | -------------------------------------------------------------------------------- /config/locales/time_of_day.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | time_of_day: 3 | formats: 4 | default: '%k:%M' 5 | hour: '%k' 6 | -------------------------------------------------------------------------------- /lib/time_of_day_attr.rb: -------------------------------------------------------------------------------- 1 | module TimeOfDayAttr 2 | DEFAULT_FORMATS = %i[default hour].freeze 3 | 4 | autoload :ActiveRecordExtension, 'time_of_day_attr/active_record_extension' 5 | autoload :FormBuilderExtension, 'time_of_day_attr/form_builder_extension' 6 | autoload :Seconds, 'time_of_day_attr/seconds' 7 | autoload :TimeFormat, 'time_of_day_attr/time_format' 8 | autoload :TimeOfDay, 'time_of_day_attr/time_of_day' 9 | 10 | require 'i18n' 11 | I18n.load_path << File.expand_path('../config/locales/time_of_day.en.yml', __dir__) 12 | I18n.load_path << File.expand_path('../config/locales/time_of_day.de.yml', __dir__) 13 | 14 | def self.delocalize(time_of_day, options = {}) 15 | TimeOfDay.convert_to_seconds(time_of_day, options) 16 | end 17 | 18 | def self.localize(seconds, options = {}) 19 | Seconds.convert_to_time_of_day(seconds, options) 20 | end 21 | singleton_class.send(:alias_method, :l, :localize) 22 | end 23 | 24 | require 'time_of_day_attr/railtie' if defined?(Rails) 25 | -------------------------------------------------------------------------------- /lib/time_of_day_attr/active_record_extension.rb: -------------------------------------------------------------------------------- 1 | module TimeOfDayAttr 2 | module ActiveRecordExtension 3 | extend ActiveSupport::Concern 4 | 5 | module ClassMethods 6 | def time_of_day_attr(*attrs) 7 | options = attrs.extract_options! 8 | 9 | writers = AttrWriterModule.new(attrs, options) 10 | 11 | if options[:prepend] 12 | prepend writers 13 | else 14 | include writers 15 | end 16 | end 17 | end 18 | end 19 | 20 | module AttrWriterModule 21 | # rubocop:disable Metrics/MethodLength 22 | def self.new(attrs, options) 23 | Module.new do 24 | attrs.each do |attr| 25 | define_method("#{attr}=") do |value| 26 | if value.is_a?(String) 27 | delocalized_value = TimeOfDayAttr.delocalize(value, options) 28 | super(delocalized_value) 29 | else 30 | super(value) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | # rubocop:enable Metrics/MethodLength 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/time_of_day_attr/form_builder_extension.rb: -------------------------------------------------------------------------------- 1 | module TimeOfDayAttr 2 | module FormBuilderExtension 3 | extend ActiveSupport::Concern 4 | 5 | def time_of_day_field(method, options = {}) 6 | options[:value] ||= begin 7 | value = object.public_send(method) 8 | localize_options = options.extract!(:format, :omit_minutes_at_full_hour) 9 | TimeOfDayAttr.localize(value, localize_options) 10 | end 11 | text_field(method, options) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/time_of_day_attr/railtie.rb: -------------------------------------------------------------------------------- 1 | module TimeOfDayAttr 2 | class Railtie < ::Rails::Railtie 3 | initializer 'time_of_day_attr.initialize' do 4 | ActiveSupport.on_load(:active_record) do 5 | include TimeOfDayAttr::ActiveRecordExtension 6 | end 7 | ActiveSupport.on_load(:action_view) do 8 | ActionView::Helpers::FormBuilder.send(:include, TimeOfDayAttr::FormBuilderExtension) 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/time_of_day_attr/seconds.rb: -------------------------------------------------------------------------------- 1 | module TimeOfDayAttr 2 | module Seconds 3 | def self.convert_to_time_of_day(value, options = {}) 4 | return value unless value.respond_to?(:seconds) 5 | 6 | format = options[:format] || DEFAULT_FORMATS.first 7 | time_format = TimeFormat.translate_format(format) 8 | 9 | time_of_day = seconds_to_time_of_day(value, time_format) 10 | if options[:omit_minutes_at_full_hour] 11 | TimeOfDay.omit_minutes_at_full_hour(time_of_day) 12 | else 13 | time_of_day 14 | end 15 | end 16 | 17 | def self.seconds_to_time_of_day(value, time_format) 18 | # Switch to beginning of year to prevent wrong conversion on the day of time change 19 | # see https://en.wikipedia.org/wiki/Daylight_saving_time 20 | time = Time.current.beginning_of_year.at_midnight + value.seconds 21 | 22 | time_of_day = time.strftime(time_format) 23 | 24 | if 24.hours.to_i == value 25 | time_of_day.gsub(' 0', '24') 26 | else 27 | time_of_day 28 | end 29 | end 30 | private_class_method :seconds_to_time_of_day 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/time_of_day_attr/time_format.rb: -------------------------------------------------------------------------------- 1 | module TimeOfDayAttr 2 | module TimeFormat 3 | def self.translate_format(format) 4 | translate = format.is_a?(Symbol) 5 | translate ? I18n.translate("time_of_day.formats.#{format}") : format 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/time_of_day_attr/time_of_day.rb: -------------------------------------------------------------------------------- 1 | module TimeOfDayAttr 2 | module TimeOfDay 3 | def self.convert_to_seconds(time_of_day, options = {}) 4 | formats = options[:formats] || DEFAULT_FORMATS 5 | catch(:out_of_range) do 6 | formats.each do |format| 7 | time_format = TimeFormat.translate_format(format) 8 | seconds = time_of_day_to_seconds(time_of_day, time_format) 9 | return seconds if seconds 10 | end 11 | end 12 | nil 13 | end 14 | 15 | def self.omit_minutes_at_full_hour(time_of_day) 16 | time_of_day.end_with?('00') ? time_of_day[0...-3] : time_of_day 17 | end 18 | 19 | def self.seconds_since_midnight(time_of_day, time) 20 | seconds = time.seconds_since_midnight 21 | seconds = 24.hours if time_of_day_24_00?(time_of_day, seconds) 22 | seconds.to_i 23 | end 24 | private_class_method :seconds_since_midnight 25 | 26 | def self.time_of_day_24_00?(time_of_day, seconds) 27 | time_of_day.starts_with?('24') && seconds.zero? 28 | end 29 | private_class_method :time_of_day_24_00? 30 | 31 | def self.time_of_day_to_seconds(time_of_day, time_format) 32 | time = time_of_day_to_time(time_of_day, time_format) 33 | return unless time 34 | seconds_since_midnight(time_of_day, time) 35 | end 36 | private_class_method :time_of_day_to_seconds 37 | 38 | def self.time_of_day_to_time(time_of_day, time_format) 39 | time = Time.strptime(time_of_day, time_format) 40 | # Switch to beginning of year to prevent wrong conversion on the day of time change 41 | # see https://en.wikipedia.org/wiki/Daylight_saving_time 42 | time.change(month: 1, day: 1) 43 | rescue ArgumentError => e 44 | throw(:out_of_range) if e.message.include?('out of range') 45 | end 46 | private_class_method :time_of_day_to_time 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/active_record_extension_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'active_record' 3 | ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') 4 | require_relative 'schema' 5 | require_relative 'models/business_hour' 6 | 7 | class ActiveRecordExtensionTest < ActiveSupport::TestCase 8 | context 'time of day value' do 9 | setup do 10 | @business_hour = BusinessHour.new(opening: '9:00', closing: '17:00') 11 | end 12 | 13 | should 'be converted to seconds since midnight' do 14 | assert_equal 32_400, @business_hour.opening 15 | assert_equal 61_200, @business_hour.closing 16 | end 17 | end 18 | 19 | context 'hour formatted time of day value' do 20 | setup do 21 | @business_hour = BusinessHour.new(opening: '9', closing: '17') 22 | end 23 | 24 | should 'be converted to seconds since midnight' do 25 | assert_equal 32_400, @business_hour.opening 26 | assert_equal 61_200, @business_hour.closing 27 | end 28 | end 29 | 30 | context 'unsupported time of day value' do 31 | setup do 32 | @business_hour = BusinessHour.new(opening: 55_800) 33 | @business_hour.opening = 'Nine' 34 | end 35 | 36 | should 'be converted to nil' do 37 | assert_nil @business_hour.opening 38 | end 39 | end 40 | 41 | context 'non string value' do 42 | setup do 43 | @business_hour = BusinessHour.new(opening: 55_800) 44 | end 45 | 46 | should 'not be converted' do 47 | assert_equal 55_800, @business_hour.opening 48 | end 49 | end 50 | 51 | context 'prepend option' do 52 | setup do 53 | @business_hour = BusinessHour.new(opening: '9', closing: '9') 54 | end 55 | 56 | should 'be supported' do 57 | assert_equal '9', @business_hour.tracked_opening 58 | assert_equal 32_400, @business_hour.opening 59 | 60 | assert_equal 32_400, @business_hour.tracked_closing 61 | assert_equal 32_400, @business_hour.closing 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/models/business_hour.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Rails/ApplicationRecord 2 | class BusinessHour < ActiveRecord::Base 3 | # rubocop:enable Rails/ApplicationRecord 4 | include TimeOfDayAttr::ActiveRecordExtension 5 | 6 | attr_reader :tracked_opening, :tracked_closing 7 | 8 | time_of_day_attr :opening 9 | time_of_day_attr :closing, prepend: true 10 | 11 | def opening=(value) 12 | @tracked_opening = value 13 | super(value) 14 | end 15 | 16 | def closing=(value) 17 | @tracked_closing = value 18 | super(value) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/schema.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Migration.verbose = false 2 | 3 | ActiveRecord::Schema.define do 4 | create_table :business_hours, force: true do |t| 5 | t.integer :opening 6 | t.integer :closing 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] = 'test' 2 | 3 | require 'minitest/autorun' 4 | require 'shoulda/context' 5 | require 'time_of_day_attr' 6 | -------------------------------------------------------------------------------- /test/time_of_day_attr/seconds_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module TimeOfDayAttr 4 | class SecondsTest < ActiveSupport::TestCase 5 | context '::convert_to_time_of_day' do 6 | should 'convert seconds to time of day' do 7 | assert_equal ' 9:00', Seconds.convert_to_time_of_day(32_400) 8 | assert_equal '14:45', Seconds.convert_to_time_of_day(53_100) 9 | end 10 | 11 | should 'omit minutes at full hour' do 12 | assert_equal ' 9', Seconds.convert_to_time_of_day(32_400, omit_minutes_at_full_hour: true) 13 | assert_equal '14', Seconds.convert_to_time_of_day(50_400, omit_minutes_at_full_hour: true) 14 | assert_equal '14:45', Seconds.convert_to_time_of_day(53_100, omit_minutes_at_full_hour: true) 15 | end 16 | 17 | should 'convert nil to nil' do 18 | assert_nil Seconds.convert_to_time_of_day(nil) 19 | end 20 | 21 | should 'support 24 hours' do 22 | assert_equal ' 0:00', Seconds.convert_to_time_of_day(0) 23 | assert_equal '24:00', Seconds.convert_to_time_of_day(86_400) 24 | assert_equal ' 0', Seconds.convert_to_time_of_day(0, omit_minutes_at_full_hour: true) 25 | assert_equal '24', Seconds.convert_to_time_of_day(86_400, omit_minutes_at_full_hour: true) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/time_of_day_attr/time_of_day_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module TimeOfDayAttr 4 | class TimeOfDayTest < ActiveSupport::TestCase 5 | context '::convert_to_seconds' do 6 | should 'convert time of day to seconds' do 7 | assert_equal 36_000, TimeOfDay.convert_to_seconds('10:00') 8 | assert_equal 55_800, TimeOfDay.convert_to_seconds('15:30') 9 | assert_equal 61_140, TimeOfDay.convert_to_seconds('16:59') 10 | assert_equal 32_400, TimeOfDay.convert_to_seconds('9:00') 11 | assert_equal 32_400, TimeOfDay.convert_to_seconds('09:00') 12 | assert_equal 32_400, TimeOfDay.convert_to_seconds('9', formats: [:hour]) 13 | end 14 | 15 | should 'convert out of range time of day to nil' do 16 | assert_nil TimeOfDay.convert_to_seconds('25:00') 17 | assert_nil TimeOfDay.convert_to_seconds('24:30') 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /time_of_day_attr.gemspec: -------------------------------------------------------------------------------- 1 | # Describe your gem and declare its dependencies: 2 | Gem::Specification.new do |s| 3 | s.name = 'time_of_day_attr' 4 | s.version = '3.0.0' 5 | s.authors = ['Clemens Teichmann'] 6 | s.email = ['clemens_t@web.de'] 7 | s.homepage = 'https://github.com/clemenst/time_of_day_attr' 8 | s.summary = 'Time of day attributes for your Rails model' 9 | # rubocop:disable Metrics/LineLength 10 | s.description = 'This ruby gem converts time of day to seconds (since midnight) and back. The value in seconds can be used for calculations and validations.' 11 | # rubocop:enable Metrics/LineLength 12 | s.files = Dir['{config,lib}/**/*'] + ['Rakefile', 'README.md'] 13 | s.test_files = Dir['test/**/*'] 14 | s.license = 'MIT' 15 | 16 | s.add_dependency 'i18n', '>= 0.7' 17 | 18 | s.add_development_dependency 'sqlite3' 19 | end 20 | --------------------------------------------------------------------------------