├── .github └── workflows │ └── main.yml ├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── date_range_formatter.gemspec ├── lib ├── date_range_formatter.rb ├── date_range_formatter │ └── version.rb └── locale │ └── en.yml └── test ├── lib └── date_range_formatter_test.rb └── test_helper.rb /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | on: [push] 3 | jobs: 4 | test: 5 | strategy: 6 | fail-fast: false 7 | matrix: 8 | os: 9 | - ubuntu-latest 10 | - macos-latest 11 | ruby: 12 | - '2.1' 13 | - '2.7' 14 | - '3.0' 15 | - '3.1' 16 | - 'head' 17 | runs-on: ${{ matrix.os }} 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: ${{ matrix.ruby }} 23 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 24 | - run: bundle exec rake 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | *.swp 4 | *.swn 5 | *.swm 6 | .bundle 7 | .config 8 | .yardoc 9 | Gemfile.lock 10 | InstalledFiles 11 | _yardoc 12 | coverage 13 | doc/ 14 | lib/bundler/man 15 | pkg 16 | rdoc 17 | spec/reports 18 | test/tmp 19 | test/version_tmp 20 | tmp 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Mikhail Kuzmin and Mikhail Dronov 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Date Range Formatter 2 | 3 | ![Build Status](https://github.com/darkleaf/date_range_formatter/actions/workflows/main.yml/badge.svg) 4 | [![Gem Version](https://badge.fury.io/rb/date_range_formatter.svg)](http://badge.fury.io/rb/date_range_formatter) 5 | 6 | This gem makes working with dates more pretty. It works well with Ruby application and most frameworks like [Ruby on Rails](https://github.com/rails/rails "Ruby on Rails"). 7 | 8 | ## Installation 9 | Very simple. Just add this code to Gemfile: 10 | 11 | gem 'date_range_formatter' 12 | 13 | ## Usage 14 | 15 | Imagine the situation when you need to show dates of some stuff at your website. For example: 16 | 17 | 1 - 9 May 2014 18 | 2 January 2015 19 | 15 July 2016 - 13 February 2017 20 | 21 | After that you should call the module DateRangeFormatter with arguments wchich describes the range of dates and format to display. For example, we have date_beginning, date_ending and format by default: 22 | 23 | ```ruby 24 | DateRangeFormatter.format('2013-01-14', '2013-02-15') 25 | #=> '14 January - 15 February 2013' 26 | ``` 27 | 28 | Also you can use `format_range` method and enumerable object: 29 | 30 | ```ruby 31 | DateRangeFormatter.format_range(['2013-02-20', '2013-01-14', '2013-01-15']) 32 | #=> '14 January - 20 February 2013' 33 | ``` 34 | 35 | If you want to show dates by another format, you can call it: 36 | 37 | ```ruby 38 | date_beginning = Date.new(2013, 01, 14) 39 | date_ending = Date.new(2014, 02, 15) 40 | date_range_str = DateRangeFormatter.format(date_beginning, date_ending, 'short') 41 | ``` 42 | 43 | If you want to show hours, you can call it: 44 | ```ruby 45 | DateRangeFormatter.format('10:00 2013-01-14', '20:00 2013-01-14', :with_time) 46 | #=> '14 January 2013, 10am - 08pm' 47 | ``` 48 | 49 | See [predefined formats](https://github.com/darkleaf/date_range_formatter/blob/master/lib/locale/en.yml). Also you can override this formats or add your own. 50 | 51 | That's all. Enjoy your profit! 52 | 53 | ## Other 54 | 55 | This idea was appeared by looking at the [article](https://coderwall.com/p/fkg-ng). Thanks to [@mbillard](https://github.com/mbillard). 56 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rake/testtask' 3 | 4 | Rake::TestTask.new do |t| 5 | t.libs << 'test' 6 | t.pattern = "test/**/*_test.rb" 7 | end 8 | 9 | task :default => :test 10 | -------------------------------------------------------------------------------- /date_range_formatter.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'date_range_formatter/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "date_range_formatter" 8 | spec.version = DateRangeFormatter::VERSION 9 | spec.authors = ["Mikhail Kuzmin", "Mikhail Dronov"] 10 | spec.email = ["m.kuzmin@darleaf.ru"] 11 | spec.description = "This is a date range formatter with i18 support. This gem can help you make your dates at views nice-looking." 12 | spec.summary = "Date range formatter" 13 | spec.homepage = "https://github.com/darkleaf/date_range_formatter" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_dependency "activesupport", ">= 3.2.0" 22 | spec.add_development_dependency "bundler" 23 | spec.add_development_dependency "rake" 24 | spec.add_development_dependency "minitest", "~> 4.7.3" 25 | end 26 | -------------------------------------------------------------------------------- /lib/date_range_formatter.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/string/conversions.rb' 2 | require 'active_support/i18n' 3 | 4 | I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml" 5 | 6 | module DateRangeFormatter 7 | 8 | class << self 9 | def format(date_beginning, date_ending, format = :default) 10 | format_range [date_beginning, date_ending], format 11 | end 12 | 13 | def format_range(enumerable_range, format = :default) 14 | return if enumerable_range.none? 15 | sorted_range = enumerable_range.map(&:to_datetime).sort 16 | date_beginning = sorted_range.first 17 | date_ending = sorted_range.last 18 | 19 | f = Formatter.new date_beginning, date_ending, format 20 | f.to_s 21 | end 22 | end 23 | 24 | class Formatter 25 | attr_reader :from_date, :until_date, :format 26 | 27 | def initialize(from_date, until_date, format) 28 | @from_date = from_date 29 | @until_date = until_date 30 | @format = format.to_sym 31 | end 32 | 33 | def to_s 34 | return I18n.t 'same_hours', **same_hours_data.merge(scope: ['date_range', format]) if same_hours? 35 | return I18n.t 'same_days', **same_days_data.merge(scope: ['date_range', format]) if same_days? 36 | return I18n.t 'same_months', **same_months_data.merge(scope: ['date_range', format]) if same_months? 37 | return I18n.t 'same_years', **same_years_data.merge(scope: ['date_range', format]) if same_years? 38 | I18n.t 'different_components', **different_components_data.merge(scope: ['date_range', format]) 39 | end 40 | 41 | def same_hours? 42 | same_days? && from_date.hour == until_date.hour 43 | end 44 | 45 | def same_days? 46 | same_months? && from_date.day == until_date.day 47 | end 48 | 49 | def same_months? 50 | same_years? && from_date.month == until_date.month 51 | end 52 | 53 | def same_years? 54 | from_date.year == until_date.year 55 | end 56 | 57 | private 58 | def same_hours_data 59 | { 60 | hour: formatted_hour(from_date), 61 | day: from_date.day, 62 | month: formatted_month(from_date), 63 | year: formatted_year(from_date), 64 | } 65 | end 66 | 67 | def same_days_data 68 | { 69 | from_hour: formatted_hour(from_date), 70 | until_hour: formatted_hour(until_date), 71 | day: from_date.day, 72 | month: formatted_month(from_date), 73 | year: formatted_year(from_date), 74 | } 75 | end 76 | 77 | def same_months_data 78 | { 79 | from_day: from_date.day, 80 | until_day: until_date.day, 81 | month: formatted_month(from_date), 82 | year: formatted_year(from_date), 83 | } 84 | end 85 | 86 | def same_years_data 87 | { 88 | from_day: from_date.day, 89 | until_day: until_date.day, 90 | from_month: formatted_month(from_date), 91 | until_month: formatted_month(until_date), 92 | year: formatted_year(from_date), 93 | } 94 | end 95 | 96 | def different_components_data 97 | { 98 | from_day: from_date.day, 99 | until_day: until_date.day, 100 | from_month: formatted_month(from_date), 101 | until_month: formatted_month(until_date), 102 | from_year: formatted_year(from_date), 103 | until_year: formatted_year(until_date), 104 | } 105 | end 106 | 107 | def formatted_hour(date) 108 | format_str = I18n.t "hour", scope: ["date_range", format] 109 | I18n.l date, format: format_str 110 | end 111 | 112 | def formatted_month(date) 113 | format_str = I18n.t "month", scope: ["date_range", format] 114 | I18n.l date, format: format_str 115 | end 116 | 117 | def formatted_year(date) 118 | format_str = I18n.t "year", scope: ["date_range", format] 119 | I18n.l date, format: format_str 120 | end 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /lib/date_range_formatter/version.rb: -------------------------------------------------------------------------------- 1 | module DateRangeFormatter 2 | VERSION = "0.1.2" 3 | end 4 | -------------------------------------------------------------------------------- /lib/locale/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | date_range: 3 | default: 4 | month: "%B" 5 | year: "%Y" 6 | same_hours: "%{day} %{month} %{year}" 7 | same_days: "%{day} %{month} %{year}" 8 | same_months: "%{from_day}-%{until_day} %{month} %{year}" 9 | same_years: "%{from_day} %{from_month} - %{until_day} %{until_month} %{year}" 10 | different_components: "%{from_day} %{from_month} %{from_year} - %{until_day} %{until_month} %{until_year}" 11 | 12 | with_time: 13 | hour: "%I%P" 14 | month: "%B" 15 | year: "%Y" 16 | same_hours: "%{day} %{month} %{year}, %{hour}" 17 | same_days: "%{day} %{month} %{year}, %{from_hour} - %{until_hour}" 18 | same_months: "%{from_day}-%{until_day} %{month} %{year}" 19 | same_years: "%{from_day} %{from_month} - %{until_day} %{until_month} %{year}" 20 | different_components: "%{from_day} %{from_month} %{from_year} - %{until_day} %{until_month} %{until_year}" 21 | -------------------------------------------------------------------------------- /test/lib/date_range_formatter_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DateRangeFormatterTest < TestHelper 4 | def setup 5 | end 6 | 7 | def test_same_days 8 | date_range_str = DateRangeFormatter.format('2013-01-14', '2013-01-14') 9 | 10 | assert_equal '14 January 2013', date_range_str 11 | end 12 | 13 | def test_same_months 14 | date_beginning = Date.new(2013, 01, 14) 15 | date_ending = Date.new(2013, 01, 15) 16 | date_range_str = DateRangeFormatter.format(date_beginning, date_ending) 17 | 18 | assert_equal '14-15 January 2013', date_range_str 19 | end 20 | 21 | def test_same_years 22 | date_beginning = Date.new(2013, 01, 14) 23 | date_ending = Date.new(2013, 02, 15) 24 | date_range_str = DateRangeFormatter.format(date_beginning, date_ending) 25 | 26 | assert_equal '14 January - 15 February 2013', date_range_str 27 | end 28 | 29 | def test_all_date_components_are_different 30 | date_beginning = Date.new(2013, 01, 14) 31 | date_ending = Date.new(2014, 02, 15) 32 | date_range_str = DateRangeFormatter.format(date_beginning, date_ending) 33 | 34 | assert_equal '14 January 2013 - 15 February 2014', date_range_str 35 | end 36 | 37 | def test_same_hours_with_time_format 38 | date_range_str = DateRangeFormatter.format('10:00 2013-01-14', '10:00 2013-01-14', :with_time) 39 | 40 | assert_equal '14 January 2013, 10am', date_range_str 41 | end 42 | 43 | def test_same_days_with_time_format 44 | date_range_str = DateRangeFormatter.format('10:00 2013-01-14', '20:00 2013-01-14', :with_time) 45 | 46 | assert_equal '14 January 2013, 10am - 08pm', date_range_str 47 | end 48 | end 49 | 50 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'minitest/unit' 3 | require 'minitest/autorun' 4 | require 'minitest/pride' 5 | 6 | Bundler.require 7 | 8 | class TestHelper < Minitest::Unit::TestCase 9 | end 10 | --------------------------------------------------------------------------------