1

DESCRIPTION

I need to get input as a string date that can be complete date-time format with timezone, it could be just date, it could be just time in 24 hour format, time with timezone in 24 hour format, or a time in am and pm or any combination of above.

As a output I want to get current(or given) date along with time in 24 hour format and a timezone, which we can parse in a ruby Time.parse(). I have done some work and you can find source code below. I have also added rspec (test case) for some of the expectation.

You can provide your own solution or update my source as you like.

EXAMPLES

NOTE: here expected date is current date if date not provided

  1. '2200+5' should get converted to "2016-1-1T22:00:00 +0500"
  2. '100' should get converted to "2016-1-1T01:00:00 +0000"
  3. '1700+8' should get converted to "2016-1-1T17:00:00 +0800"
  4. '5pm+8' should get converted to "2016-1-1T17:00:00 +0800"
  5. '5am+8' should get converted to "2016-1-1T5:00:00 +0800"
  6. '2016-12-15T2300+5' should get converted to '2016-12-15T23:00:00 +0500'
  7. '1730' should get converted to "2016-1-1T17:30:00 +0000"
  8. '530pm+8' should get converted to "2016-1-1T17:30:00 +0800"
  9. '1210am+8' should get converted to "2016-1-1T12:10:00 +0800"

RSpec

describe "should convert datetime to accepted format"
    Timecop.freeze(Time.utc(2016,1,1,2,0,0))
    it {expect(parse('2200+5')).to eq({time: Time.parse("2016-1-1T22:00:00 +0500")})}
    it {expect(parse('100')).to eq({time: Time.parse("2016-1-1T01:00:00 +0000")})}
    it {expect(parse('1700+8')).to eq({time: Time.parse("2016-1-1T17:00:00 +0800")})}
    it {expect(parse('5pm+8')).to eq({time: Time.parse("2016-1-1T17:00:00 +0800")})}
    it {expect(parse('5am+8')).to eq({time: Time.parse("2016-1-1T5:00:00 +0800")})}
    it {expect(parse('2016-12-15T2300+5')).to eq({time: Time.parse("2016-12-15T23:00:00 +0500")})}
    it {expect(parse('1730')).to eq({time: Time.parse("2016-1-1T17:30:00 +0000")})}
    Timecop.return
end

WHAT I HAVE DONE SO FAR

# ensure 2 char in time format
def ensure_2_char_in_time(t)
  return "0#{t}" if t.to_s.length == 1
  t.to_s
end


def get_time_zone_from(aTimeParams)
  date, tzm, tzm_type = nil

  # get date, time, timezon from given string
  if aTimeParams.include?('-') && aTimeParams.include?('T')
      date, time_zone = aTimeParams.split('T')
  else
    time_zone = aTimeParams
  end

  # get time, zone from given string
  if time_zone.include?('+')
    tzm_type = '+'
    time_str, tzm = aTimeParams.split('+')
  elsif time_zone.include?('-')
    tzm_type = '-'
    time_str, tzm = aTimeParams.split('-')
  else
    tzm_type = '+'
    time_str = aTimeParams
  end
  date = "#{Date.today.year}-#{Date.today.month}-#{Date.today.day}" if date.blank?

  if time_str.include?('pm')
      hour = ensure_2_char_in_time(time_str.to_i + 12)
      min = '00'
  elsif time_str.include?('am')
    hour = ensure_2_char_in_time(time_str.to_i)
    min = '00'
  else
    hour = ensure_2_char_in_time(time_str.to_i / 100)
    min = ensure_2_char_in_time(time_str.to_i % 100)
  end

  if tzm.to_s.length <= 2
    tzm_h = ensure_2_char_in_time tzm.to_i % 100
    tzm_m = "00"
  else
    tzm_h = ensure_2_char_in_time tzm.to_i / 100
    tzm_m = ensure_2_char_in_time tzm.to_i % 100
  end

  {
      time: Time.parse("#{date}T#{hour}:#{min}:00 #{tzm_type}#{tzm_h}:#{tzm_m}"),
      tzm: (tzm_h.to_i * 60 + tzm_m.to_i)
  }
end

Please let me know for further clarification.

Thanks

1
  • Thanks for the suggestion, let me do that Commented Dec 9, 2016 at 8:02

1 Answer 1

1

This code works fine for the examples you mentioned, and then some :

module FuzzyTimeParse
  refine String do
    # Removes the first regex match from self.
    # Returns the matching substring if found, nil otherwise
    def extract!(regex)
      sub!(regex, '')
      $&
    end
  end

  refine Time.singleton_class do
    def fuzzy_parse(date_or_time_or_something)
      input = date_or_time_or_something.dup

      input.extract!('am')
      pm = input.extract!('pm') ? 12 : 0

      timezone = input.extract!(/\s*(\+|-)\d$/).to_f || 0

      timezone_hour = timezone.to_i
      timezone_min  = timezone * 60 % 60

      if hour = input.extract!(/(?<![\d\:\-])\d\d?$/)
        min   = 0
      else
        min   = input.extract!(/(?<!-)\d\d$/) || 0
        input.extract!(':')
        hour  = input.extract!(/(?<![-\d])\d\d?$/) || 0
      end

      input.extract!(/T$/)
      input.gsub!(/\s*/,'')
      date = input.extract!(/\d\d\d\d\D?\d\d\D?\d\d/) || Time.now.strftime('%Y-%m-%d')

      $stderr.puts "Warning : #{input} is still not parsed" unless input.empty?
      date_time = format('%sT%02d:%02d:00 +%02d%02d', date, hour.to_i + pm, min, timezone_hour, timezone_min)

      { time: Time.parse(date_time) }
    end
  end
end

require 'timecop'

using FuzzyTimeParse

describe 'convert datetime to accepted format' do
  before do
    Timecop.freeze(Time.local(2016, 1, 1, 2, 0, 0))
  end

  it { expect(Time.fuzzy_parse('2200+5')).to eq(time: Time.parse('2016-1-1T22:00:00 +0500')) }
  it { expect(Time.fuzzy_parse('100')).to eq(time: Time.parse('2016-1-1T01:00:00 +0000')) }
  it { expect(Time.fuzzy_parse('1700+8')).to eq(time: Time.parse('2016-1-1T17:00:00 +0800')) }
  it { expect(Time.fuzzy_parse('5pm+8')).to eq(time: Time.parse('2016-1-1T17:00:00 +0800')) }
  it { expect(Time.fuzzy_parse('5am+8')).to eq(time: Time.parse('2016-1-1T5:00:00 +0800')) }
  it { expect(Time.fuzzy_parse('2016-12-15T2300+5')).to eq(time: Time.parse('2016-12-15T23:00:00 +0500')) }
  it { expect(Time.fuzzy_parse('2016-12-15 2300')).to eq(time: Time.parse('2016-12-15T23:00:00 +0000')) }
  it { expect(Time.fuzzy_parse('2016-12-15 23:10 +7')).to eq(time: Time.parse('2016-12-15T23:10:00 +0700')) }
  it { expect(Time.fuzzy_parse('1730')).to eq(time: Time.parse('2016-1-1T17:30:00 +0000')) }
  it { expect(Time.fuzzy_parse('1210am+8')).to eq(time: Time.parse('2016-1-1T12:10:00 +0800')) }
  it { expect(Time.fuzzy_parse('530pm+8')).to eq(time: Time.parse('2016-1-1T17:30:00 +0800')) }
  it { expect(Time.fuzzy_parse('1730')).to eq(time: Time.parse('2016-1-1T17:30:00 +0000')) }
  it { expect(Time.fuzzy_parse('17:30')).to eq(time: Time.parse('2016-1-1T17:30:00 +0000')) }
  it { expect(Time.fuzzy_parse('17:30 +5')).to eq(time: Time.parse('2016-1-1T17:30:00 +0500')) }
  it { expect(Time.fuzzy_parse('2016-12-03')).to eq(time: Time.parse('2016-12-03T00:00:00 +0000')) }
  it { expect(Time.fuzzy_parse('2016-12-03 +2')).to eq(time: Time.parse('2016-12-03T00:00:00 +0200')) }

  after do
    Timecop.return
  end
end

It outputs :

16 examples, 0 failures

If you don't want to reinvent the wheel, Chronic gem could help you, even though it looks like the timezone needs to be defined manually : Chronic.time_class = Time.zone

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Eric for your solution

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.