przbadu
przbadu

Reputation: 6049

Working with date time string

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

Upvotes: 1

Views: 259

Answers (1)

Eric Duminil
Eric Duminil

Reputation: 54243

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

Upvotes: 1

Related Questions