Module: Sisimai::DateTime

Defined in:
lib/sisimai/datetime.rb

Overview

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

Constant Summary collapse

TZ_OFFSET =

Max time zone offset, 54000 seconds

54000
MonthName =
{
  full: %w[January February March April May June July August September October November December],
  abbr: %w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec],
}.freeze
DayOfWeek =
{
  full: %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday],
  abbr: %w[Sun Mon Tue Wed Thu Fri Sat],
}.freeze
TimeZones =
{
  # http://en.wikipedia.org/wiki/List_of_time_zone_abbreviations
  #'ACDT' => '+1030', # Australian Central Daylight Time  UTC+10:30
  #'ACST' => '+0930', # Australian Central Standard Time  UTC+09:30
  #'ACT'  => '+0800', # ASEAN Common Time                 UTC+08:00
  'ADT'   => '-0300', # Atlantic Daylight Time            UTC-03:00
  #'AEDT' => '+1100', # Australian Eastern Daylight Time  UTC+11:00
  #'AEST' => '+1000', # Australian Eastern Standard Time  UTC+10:00
  #'AFT'  => '+0430', # Afghanistan Time                  UTC+04:30
  'AKDT'  => '-0800', # Alaska Daylight Time              UTC-08:00
  'AKST'  => '-0900', # Alaska Standard Time              UTC-09:00
  #'AMST' => '+0500', # Armenia Summer Time               UTC+05:00
  #'AMT'  => '+0400', # Armenia Time                      UTC+04:00
  #'ART'  => '-0300', # Argentina Time                    UTC+03:00
  #'AST'  => '+0300', # Arab Standard Time (Kuwait, Riyadh)       UTC+03:00
  #'AST'  => '+0400', # Arabian Standard Time (Abu Dhabi, Muscat) UTC+04:00
  #'AST'  => '+0300', # Arabic Standard Time (Baghdad)    UTC+03:00
  'AST'   => '-0400', # Atlantic Standard Time            UTC-04:00
  #'AWDT' => '+0900', # Australian Western Daylight Time  UTC+09:00
  #'AWST' => '+0800', # Australian Western Standard Time  UTC+08:00
  #'AZOST'=> '-0100', # Azores Standard Time              UTC-01:00
  #'AZT'  => '+0400', # Azerbaijan Time                   UTC+04:00
  #'BDT'  => '+0800', # Brunei Time                       UTC+08:00
  #'BIOT' => '+0600', # British Indian Ocean Time         UTC+06:00
  #'BIT'  => '-1200', # Baker Island Time                 UTC-12:00
  #'BOT'  => '-0400', # Bolivia Time                      UTC-04:00
  #'BRT'  => '-0300', # Brasilia Time                     UTC-03:00
  #'BST'  => '+0600', # Bangladesh Standard Time          UTC+06:00
  #'BST'  => '+0100', # British Summer Time (British Standard Time from Feb 1968 to Oct 1971) UTC+01:00
  #'BTT'  => '+0600', # Bhutan Time                       UTC+06:00
  #'CAT'  => '+0200', # Central Africa Time               UTC+02:00
  #'CCT'  => '+0630', # Cocos Islands Time                UTC+06:30
  'CDT'   => '-0500', # Central Daylight Time (North America)     UTC-05:00
  #'CEDT' => '+0200', # Central European Daylight Time    UTC+02:00
  #'CEST' => '+0200', # Central European Summer Time      UTC+02:00
  #'CET'  => '+0100', # Central European Time             UTC+01:00
  #'CHAST'=> '+1245', # Chatham Standard Time             UTC+12:45
  #'CIST' => '-0800', # Clipperton Island Standard Time   UTC-08:00
  #'CKT'  => '-1000', # Cook Island Time                  UTC-10:00
  #'CLST' => '-0300', # Chile Summer Time                 UTC-03:00
  #'CLT'  => '-0400', # Chile Standard Time               UTC-04:00
  #'COST' => '-0400', # Colombia Summer Time              UTC-04:00
  #'COT'  => '-0500', # Colombia Time                     UTC-05:00
  'CST'   => '-0600', # Central Standard Time (North America) UTC-06:00
  #'CST'  => '+0800', # China Standard Time               UTC+08:00
  #'CVT'  => '-0100', # Cape Verde Time                   UTC-01:00
  #'CXT'  => '+0700', # Christmas Island Time             UTC+07:00
  #'ChST' => '+1000', # Chamorro Standard Time            UTC+10:00
  # 'DST' => ''       # Daylight saving time              Depending
  #'DFT'  => '+0100', # AIX specific equivalent of Central European Time  UTC+01:00
  #'EAST' => '-0600', # Easter Island Standard Time       UTC-06:00
  #'EAT'  => '+0300', # East Africa Time                  UTC+03:00
  #'ECT'  => '-0400', # Eastern Caribbean Time (does not recognise DST)   UTC-04:00
  #'ECT'  => '-0500', # Ecuador Time                      UTC-05:00
  'EDT'   => '-0400', # Eastern Daylight Time (North America)     UTC-04:00
  #'EEDT' => '+0300', # Eastern European Daylight Time    UTC+03:00
  #'EEST' => '+0300', # Eastern European Summer Time      UTC+03:00
  #'EET'  => '+0200', # Eastern European Time             UTC+02:00
  'EST'   => '+0500', # Eastern Standard Time (North America) UTC-05:00
  #'FJT'  => '+1200', # Fiji Time                         UTC+12:00
  #'FKST' => '-0400', # Falkland Islands Standard Time    UTC-04:00
  #'GALT' => '-0600', # Galapagos Time                    UTC-06:00
  #'GET'  => '+0400', # Georgia Standard Time             UTC+04:00
  #'GFT'  => '-0300', # French Guiana Time                UTC-03:00
  #'GILT' => '+1200', # Gilbert Island Time               UTC+12:00
  #'GIT'  => '-0900', # Gambier Island Time               UTC-09:00
  'GMT'   => '+0000', # Greenwich Mean Time               UTC
  #'GST'  => '-0200', # South Georgia and the South Sandwich Islands  UTC-02:00
  #'GYT'  => '-0400', # Guyana Time                       UTC-04:00
  'HADT'  => '-0900', # Hawaii-Aleutian Daylight Time     UTC-09:00
  'HAST'  => '-1000', # Hawaii-Aleutian Standard Time     UTC-10:00
  #'HKT'  => '+0800', # Hong Kong Time                    UTC+08:00
  #'HMT'  => '+0500', # Heard and McDonald Islands Time   UTC+05:00
  'HST'   => '-1000', # Hawaii Standard Time              UTC-10:00
  #'IRKT' => '+0800', # Irkutsk Time                      UTC+08:00
  #'IRST' => '+0330', # Iran Standard Time                UTC+03:30
  #'IST'  => '+0530', # Indian Standard Time              UTC+05:30
  #'IST'  => '+0100', # Irish Summer Time                 UTC+01:00
  #'IST'  => '+0200', # Israel Standard Time              UTC+02:00
  'JST'   => '+0900', # Japan Standard Time               UTC+09:00
  #'KRAT' => '+0700', # Krasnoyarsk Time                  UTC+07:00
  #'KST'  => '+0900', # Korea Standard Time               UTC+09:00
  #'LHST' => '+1030', # Lord Howe Standard Time           UTC+10:30
  #'LINT' => '+1400', # Line Islands Time                 UTC+14:00
  #'MAGT' => '+1100', # Magadan Time                      UTC+11:00
  'MDT'   => '-0600', # Mountain Daylight Time(North America) UTC-06:00
  #'MIT'  => '-0930', # Marquesas Islands Time            UTC-09:30
  #'MSD'  => '+0400', # Moscow Summer Time                UTC+04:00
  #'MSK'  => '+0300', # Moscow Standard Time              UTC+03:00
  #'MST'  => '+0800', # Malaysian Standard Time           UTC+08:00
  'MST'   => '-0700', # Mountain Standard Time(North America) UTC-07:00
  #'MST'  => '+0630', # Myanmar Standard Time             UTC+06:30
  #'MUT'  => '+0400', # Mauritius Time                    UTC+04:00
  #'NDT'  => '-0230', # Newfoundland Daylight Time        UTC-02:30
  #'NFT'  => '+1130', # Norfolk Time[1]                   UTC+11:30
  #'NPT'  => '+0545', # Nepal Time                        UTC+05:45
  #'NST'  => '-0330', # Newfoundland Standard Time        UTC-03:30
  #'NT'   => '-0330', # Newfoundland Time                 UTC-03:30
  #'OMST' => '+0600', # Omsk Time                         UTC+06:00
  'PDT'   => '-0700', # Pacific Daylight Time(North America)  UTC-07:00
  #'PETT' => '+1200', # Kamchatka Time                    UTC+12:00
  #'PHOT' => '+1300', # Phoenix Island Time               UTC+13:00
  #'PKT'  => '+0500', # Pakistan Standard Time            UTC+05:00
  'PST'   => '-0800', # Pacific Standard Time (North America) UTC-08:00
  #'PST'  => '+0800', # Philippine Standard Time          UTC+08:00
  #'RET'  => '+0400', # Reunion Time                      UTC+04:00
  #'SAMT' => '+0400', # Samara Time                       UTC+04:00
  #'SAST' => '+0200', # South African Standard Time       UTC+02:00
  #'SBT'  => '+1100', # Solomon Islands Time              UTC+11:00
  #'SCT'  => '+0400', # Seychelles Time                   UTC+04:00
  #'SLT'  => '+0530', # Sri Lanka Time                    UTC+05:30
  #'SST'  => '-1100', # Samoa Standard Time               UTC-11:00
  #'SST'  => '+0800', # Singapore Standard Time           UTC+08:00
  #'TAHT' => '-1000', # Tahiti Time                       UTC-10:00
  #'THA'  => '+0700', # Thailand Standard Time            UTC+07:00
  'UT'    => '-0000', # Coordinated Universal Time        UTC
  'UTC'   => '-0000', # Coordinated Universal Time        UTC
  #'UYST' => '-0200', # Uruguay Summer Time               UTC-02:00
  #'UYT'  => '-0300', # Uruguay Standard Time             UTC-03:00
  #'VET'  => '-0430', # Venezuelan Standard Time          UTC-04:30
  #'VLAT' => '+1000', # Vladivostok Time                  UTC+10:00
  #'WAT'  => '+0100', # West Africa Time                  UTC+01:00
  #'WEDT' => '+0100', # Western European Daylight Time    UTC+01:00
  #'WEST' => '+0100', # Western European Summer Time      UTC+01:00
  #'WET'  => '-0000', # Western European Time             UTC
  #'YAKT' => '+0900', # Yakutsk Time                      UTC+09:00
  #'YEKT' => '+0500', # Yekaterinburg Time                UTC+05:00
}.freeze

Class Method Summary collapse

Class Method Details

.abbr2tz(argv1) ⇒ String

Abbreviation -> Tiemzone

Examples:

Get the timezone string of β€œJST”

abbr2tz('JST')  #=> '+0900'

Parameters:

  • argv1 (String)

    Abbr. e.g.) JST, GMT, PDT

Returns:

  • (String)

    0900, 0000, -0600 or an empty string if the argument is invalid format or not supported abbreviation



321
322
323
324
# File 'lib/sisimai/datetime.rb', line 321

def abbr2tz(argv1)
  return "" if argv1.is_a?(::String) == false
  return TimeZones[argv1] || ""
end

.monthname(argv1 = false) ⇒ Array, String

Month name list

Examples:

Get the names of each month

monthname()     #=> [ 'Jan', 'Feb', ... ]
monthname(true) #=> [ 'January', 'February', 'March', ... ]

Parameters:

  • argv1 (Boolean) (defaults to: false)

    Require full name or not

Returns:

  • (Array, String)

    Month name list or month name



153
154
155
156
# File 'lib/sisimai/datetime.rb', line 153

def monthname(argv1 = false)
  value = argv1 ? :full : :abbr
  return MonthName[value]
end

.parse(argv1) ⇒ String

Parse date string; strptime() wrapper

Examples:

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

Parameters:

  • argv1 (String)

    Date string

Returns:

  • (String)

    Converted date string

See Also:



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/sisimai/datetime.rb', line 166

def parse(argv1)
  return "" if argv1.is_a?(::String) == false || argv1.empty?

  datestring = argv1.sub(/[,](\d+)/, ', \1').sub(/(\d{1,2}),/, '\1')
  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[-1, 1] = '' 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 if v[:Y].nil?
        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.downcase == 'am' || p.downcase == 'pm'
      # 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); v[:z] = "+0000" if v[:z].empty?
      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 ""
  end
  return "" if v[:Y].to_i < 1902 || v[:Y].to_i > 2037 # -(2^31) ~ (2^31)

  # 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) ⇒ String

Convert to Timezone string

Examples:

Get timezone offset string of specified seconds

second2tz(12345)    #=> '+0325'

Parameters:

  • argv1 (Integer)

    Second to be converted

Returns:

  • (String)

    Timezone offset string

See Also:



363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/sisimai/datetime.rb', line 363

def second2tz(argv1)
  return '+0000' if argv1.is_a?(::Integer) == false
  return "" 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

.tz2second(argv1) ⇒ Integer

Convert to second

Examples:

Convert β€˜+0900’ to seconds

tz2second('+0900')  #=> 32400

Parameters:

  • argv1 (String)

    Timezone string e.g) +0900

Returns:

  • (Integer)

    n: seconds or 0 when the argument is invalid format

See Also:



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/sisimai/datetime.rb', line 332

def tz2second(argv1)
  return 0 if argv1.is_a?(::String) == false
  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 0 if ztime.abs > TZ_OFFSET
    return ztime

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