module Sisimai::DateTime

Sisimai::DateTime provide methods for dealing date and time.

Constants

BASE_D
BASE_L
BASE_Y
CONST_E
CONST_P
DayOfWeek
MathematicalConstant
MonthName
TZ_OFFSET
TimeUnit
TimeZones

Public Class Methods

abbr2tz(argv1) click to toggle source

Abbreviation -> Tiemzone @param [String] argv1 Abbr. e.g.) JST, GMT, PDT @return [String, Nil] +0900, +0000, -0600 or nil if the argument is

invalid format or not supported abbreviation

@example Get the timezone string of “JST”

abbr2tz('JST')  #=> '+0900'
# File lib/sisimai/datetime.rb, line 401
def abbr2tz(argv1)
  return nil unless argv1.is_a?(::String)
  return TimeZones[argv1]
end
dayofweek(argv1 = false) click to toggle source

List of day of week @param [Boolean] argv1 Require full name @return [Array, String] List of day of week or day of week @example Get the names of each day of week

dayofweek()     #=> [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
dayofweek(true) #=> [ 'Sunday', 'Monday', 'Tuesday', ... ]
# File lib/sisimai/datetime.rb, line 226
def dayofweek(argv1 = false)
  value = argv1 ? :full : :abbr
  return DayOfWeek[value]
end
monthname(argv1 = false) click to toggle source

Month name list @param [Boolean] argv1 Require full name or not @return [Array, String] Month name list or month name @example Get the names of each month

monthname()     #=> [ 'Jan', 'Feb', ... ]
monthname(true) #=> [ 'January', 'February', 'March', ... ]
# File lib/sisimai/datetime.rb, line 215
def monthname(argv1 = false)
  value = argv1 ? :full : :abbr
  return MonthName[value]
end
parse(argv1) click to toggle source

Parse date string; strptime() wrapper @param [String] argv1 Date string @return [String] Converted date string @see en.wikipedia.org/wiki/ISO_8601 @see www.ietf.org/rfc/rfc3339.txt @example Parse date string and convert to generic format string

parse("2015-11-03T23:34:45 Tue")    #=> Tue, 3 Nov 2015 23:34:45 +0900
parse("Tue, Nov 3 2015 2:2:2")      #=> Tue, 3 Nov 2015 02:02:02 +0900
# File lib/sisimai/datetime.rb, line 239
def parse(argv1)
  return nil unless argv1.is_a?(::String)
  return nil if argv1.empty?

  datestring = argv1
  datestring.sub!(/[,](\d+)/, ', \1') # Thu,13 -> Thu, 13
  datestring.sub!(/(\d{1,2}),/, '\1') # Apr,29 -> Apr 29
  timetokens = datestring.split(' ')
  afternoon1 = 0    # (Integer) After noon flag
  altervalue = {}   # (Hash) To store alternative values
  v = {
    Y: nil, # (Integer) Year
    M: nil, # (String) Month Abbr.
    d: nil, # (Integer) Day
    a: nil, # (String) Day of week, Abbr.
    T: nil, # (String) Time
    z: nil, # (Integer) Timezone offset
  }

  while p = timetokens.shift do
    # Parse each piece of time
    if p =~ /\A[A-Z][a-z]{2,}[,]?\z/
      # Day of week or Day of week; Thu, Apr, ...
      p.gsub!(/,\z/, '') if p.end_with?(',')  # "Thu," => "Thu"
      p = p[0,3] if p.size > 3

      if DayOfWeek[:abbr].include?(p)
        # Day of week; Mon, Thu, Sun,...
        v[:a] = p

      elsif MonthName[:abbr].include?(p)
        # Month name abbr.; Apr, May, ...
        v[:M] = p
      end
    elsif p =~ /\A\d{1,4}\z/
      # Year or Day; 2005, 31, 04,  1, ...
      if p.to_i > 31
        # The piece is the value of an year
        v[:Y] = p.to_i
      else
        # The piece is the value of a day
        if v[:d]
          # 2-digit year?
          altervalue[:Y] = p unless v[:Y]
        else
          # The value is "day"
          v[:d] = p
        end
      end
    elsif cr = p.match(/\A([0-2]\d):([0-5]\d):([0-5]\d)\z/) ||
               p.match(/\A(\d{1,2})[-:](\d{1,2})[-:](\d{1,2})\z/)
      # Time; 12:34:56, 03:14:15, ...
      # Arrival-Date: 2014-03-26 00-01-19
      if cr[1].to_i < 24 && cr[2].to_i < 60 && cr[3].to_i < 60
        # Valid time format, maybe...
        v[:T] = sprintf('%02d:%02d:%02d', cr[1].to_i, cr[2].to_i, cr[3].to_i)
      end
    elsif cr = p.match(/\A([0-2]\d):([0-5]\d)\z/)
      # Time; 12:34 => 12:34:00
      if cr[1].to_i < 24 && cr[2].to_i < 60
        v[:T] = sprintf('%02d:%02d:00', cr[1].to_i, cr[2].to_i)
      end
    elsif cr = p.match(/\A(\d\d?):(\d\d?)\z/)
      # Time: 1:4 => 01:04:00
      v[:T] = sprintf('%02d:%02d:00', cr[1].to_i, cr[2].to_i)

    elsif p =~ /\A[APap][Mm]\z/
      # AM or PM
      afternoon1 = 1
    else
      # Timezone offset and others
      if p =~ /\A[-+][01]\d{3}\z/
        # Timezone offset; +0000, +0900, -1000, ...
        v[:z] ||= p

      elsif p =~ /\A[(]?[A-Z]{2,5}[)]?\z/
        # Timezone abbreviation; JST, GMT, UTC, ...
        v[:z] ||= abbr2tz(p) || '+0000'
      else
        # Other date format
        if cr = p.match(%r|\A(\d{4})[-/](\d{1,2})[-/](\d{1,2})\z|)
          # Mail.app(MacOS X)'s faked Bounce, Arrival-Date: 2010-06-18 17:17:52 +0900
          v[:Y] = cr[1].to_i
          v[:M] = MonthName[:abbr][cr[2].to_i - 1]
          v[:d] = cr[3].to_i

        elsif cr = p.match(%r|\A(\d{4})[-/](\d{1,2})[-/](\d{1,2})T([0-2]\d):([0-5]\d):([0-5]\d)\z|)
          # ISO 8601; 2000-04-29T01:23:45
          v[:Y] = cr[1].to_i
          v[:M] = MonthName[:abbr][cr[2].to_i - 1]
          v[:d] = cr[3].to_i if cr[3].to_i < 32

          if cr[4].to_i < 24 && cr[5].to_i < 60 && cr[6].to_i < 60
            v[:T] = sprintf('%02d:%02d:%02d', cr[4].to_i, cr[5].to_i, cr[6].to_i)
          end
        elsif cr = p.match(%r|\A(\d{1,2})/(\d{1,2})/(\d{1,2})\z|)
          # 4/29/01 11:34:45 PM
          v[:M]  = MonthName[:abbr][cr[1].to_i - 1]
          v[:d]  = cr[2].to_i
          v[:Y]  = cr[3].to_i + 2000
          v[:Y] -= 100 if v[:Y].to_i > ::DateTime.now.year + 1

        elsif cr = p.match(%r|\A(\d{1,2})[-/](\d{1,2})[-/](\d{4})|)
          # 29-04-2017 22:22
          v[:d] = cr[1].to_i if cr[1].to_i < 32
          v[:M] = MonthName[:abbr][cr[2].to_i - 1]
          v[:Y] = cr[3].to_i
        end
      end
    end
  end # End of while()

  if v[:T] && afternoon1 > 0
    # +12
    t0 = v[:T]
    t1 = v[:T].split(':')
    v[:T] = sprintf('%02d:%02d:%02d', t1[0].to_i + 12, t1[1].to_i, t1[2].to_i)
    v[:T] = t0 if t1[0].to_i > 12
  end
  v[:a] ||= 'Thu' # There is no day of week

  if !v[:Y].nil? && v[:Y].to_i < 200
    # 99 -> 1999, 102 -> 2002
    v[:Y] = v[:Y].to_i + 1900
  end
  v[:z] ||= ::DateTime.now.zone.delete(':')

  # Adjust 2-digit Year
  if altervalue[:Y] && !v[:Y]
    # Check alternative value(Year)
    v[:Y] ||= if altervalue[:Y].to_i >= 82
                # SMTP was born in 1982
                1900 + altervalue[:Y].to_i
              else
                # 20XX
                2000 + altervalue[:Y].to_i
              end
  end

  # Check each piece
  if v.value?(nil)
    # Strange date format
    warn sprintf(' ***warning: Strange date format [%s]', datestring)
    return nil
  end

  if v[:Y].to_i < 1902 || v[:Y].to_i > 2037
    # -(2^31) ~ (2^31)
    return nil
  end

  # Build date string
  #   Thu, 29 Apr 2004 10:01:11 +0900
  return sprintf('%s, %s %s %s %s %s', v[:a], v[:d], v[:M], v[:Y], v[:T], v[:z])
end
second2tz(argv1) click to toggle source

Convert to Timezone string @param [Integer] argv1 Second to be converted @return [String] Timezone offset string @see tz2second @example Get timezone offset string of specified seconds

second2tz(12345)    #=> '+0325'
# File lib/sisimai/datetime.rb, line 444
def second2tz(argv1)
  return '+0000' unless argv1.is_a?(::Integer)
  return nil if argv1.abs > TZ_OFFSET  # UTC+14 + 1(DST?)

  digit = { :operator => '+' }
  digit[:operator] = '-' if argv1 < 0
  digit[:hours]    = (argv1.abs / 3600).to_i
  digit[:minutes]  = ((argv1.abs % 3600) / 60).to_i

  timez = sprintf('%s%02d%02d', digit[:operator], digit[:hours], digit[:minutes])
  return timez
end
to_second(argv1) click to toggle source

Convert to second @param [String] argv1 Digit and a unit of time @return [Integer] n: seconds

0: 0 or invalid unit of time

@example Get the value of seconds

to_second('1d') #=> 86400
to_second('2h') #=>  7200
# File lib/sisimai/datetime.rb, line 182
def to_second(argv1)
  return 0 unless argv1.is_a?(::String)

  getseconds = 0
  unitoftime = TimeUnit.keys.join
  mathconsts = MathematicalConstant.keys.join

  if cr = argv1.match(/\A(\d+|\d+[.]\d+)([#{unitoftime}])?\z/)
    # 1d, 1.5w
    n = cr[1].to_f
    u = cr[2] || 'd'
    getseconds = n * TimeUnit[u].to_f

  elsif cr = argv1.match(/\A(\d+|\d+[.]\d+)?([#{mathconsts}])([#{unitoftime}])?\z/)
    # 1pd, 1.5pw
    n = cr[1].to_f || 1
    n = 1 if n.to_i == 0
    m = MathematicalConstant[cr[2]].to_f
    u = cr[3] || 'd'
    getseconds = n * m * TimeUnit[u].to_f
  else
    getseconds = 0
  end

  return getseconds
end
tz2second(argv1) click to toggle source

Convert to second @param [String] argv1 Timezone string e.g) +0900 @return [Integer, Nil] n: seconds or nil it the argument is invalid

format string

@see second2tz @example Convert '+0900' to seconds

tz2second('+0900')  #=> 32400
# File lib/sisimai/datetime.rb, line 413
def tz2second(argv1)
  return nil unless argv1.is_a?(::String)
  ztime = 0

  if cr = argv1.match(/\A([-+])(\d)(\d)(\d{2})\z/)
    digit = {
      :'operator' => cr[1],
      :'hour-10'  => cr[2].to_i,
      :'hour-01'  => cr[3].to_i,
      :'minutes'  => cr[4].to_i,
    }
    ztime += (digit[:'hour-10'] * 10 + digit[:'hour-01']) * 3600
    ztime += (digit[:'minutes'] * 60)
    ztime *= -1 if digit[:'operator'] == '-'

    return nil if ztime.abs > TZ_OFFSET
    return ztime

  elsif argv1 =~ /\A[A-Za-z]+\z/
    return tz2second(TimeZones[argv1])
  else
    return nil
  end
end