Module: DateNamedFile::DateishHelpers

Included in:
Dateish, Template
Defined in:
lib/date_named_file/dateish.rb

Overview

Provide some simple and very naïve methods to turn something that might be a date into a date.

Constant Summary collapse

ALL_DIGITS =
/\A\d+\Z/
VALID_DELIMITERS =
/[-_ :]/

Instance Method Summary collapse

Instance Method Details

#datetime_from_parts(parts) ⇒ Object



136
137
138
139
140
141
142
143
# File 'lib/date_named_file/dateish.rb', line 136

def datetime_from_parts(parts)
  year           = parts[0]
  non_year_parts = parts[1..-1].map { |dstring| dstring.scan(/\d\d/) }.flatten
  all_parts      = non_year_parts.unshift(year).map(&:to_i)
  DateTime.new(*all_parts)
rescue ArgumentError
  raise InvalidDateFormat.new("DateTime.new rejected extracted parts ([#{parts.join(',')}]).")
end

#digit_string?(str) ⇒ Boolean

Returns:

  • (Boolean)


163
164
165
# File 'lib/date_named_file/dateish.rb', line 163

def digit_string?(str)
  ALL_DIGITS.match(str)
end

#ditch_leading_delimiters(str) ⇒ Object



180
181
182
# File 'lib/date_named_file/dateish.rb', line 180

def ditch_leading_delimiters(str)
  str.sub(/\A#{VALID_DELIMITERS}/, '')
end

#extract_delimited_datetime(str) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/date_named_file/dateish.rb', line 110

def extract_delimited_datetime(str)
  str = perform_simple_transforms(str)
  validate_delimited_datetime!(str)
  year           = extract_year(str)
  non_year_parts = extract_non_year_parts(str)
  all_parts      = non_year_parts.unshift(year)
  datetime_from_parts(all_parts)
rescue ArgumentError => e
  # presumably a DateTime parse error
  false
rescue NonTwoDigitDateParts
  raise InvalidDateFormat.new("Trying to parse as delimited date. '#{str}' looks to have non-two-digit parts (no zero padding?).")
rescue NonDigitsInDelimitedDate
  raise InvalidDateFormat.new("Trying to parse as delimited date. '#{str}' looks to have non-digits between delimiters.")
end

#extract_from_digitstring(str) ⇒ Object



59
60
61
62
# File 'lib/date_named_file/dateish.rb', line 59

def extract_from_digitstring(str)
  extract_unix_timestamp(str) or
    extract_undelimited_datetime(str)
end

#extract_non_year_parts(str) ⇒ Object



145
146
147
148
149
# File 'lib/date_named_file/dateish.rb', line 145

def extract_non_year_parts(str)
  parts = extract_rest(str).split(VALID_DELIMITERS)
  validate_parts!(parts)
  parts
end

#extract_rest(str) ⇒ Object



175
176
177
178
# File 'lib/date_named_file/dateish.rb', line 175

def extract_rest(str)
  everything_after_the_year = str[4..-1]
  ditch_leading_delimiters(everything_after_the_year)
end

#extract_undelimited_datetime(digit_string) ⇒ DateTime, FalseClass

An undelimited datetime is a string of digits at least eight digits long (to get YYYYMMDD). Anything after that is pulled out into two-digit chunks and sent along to DateTime.new as integers. This means:

  • YYYYMMDD is always necessary; everything after that is optional

  • The rest must be in order: Hour, Minute, Second

  • Everything is assumed to be two digits and zero-padded

Limitations:

  • No support for milliseconds with normal dates. Milliseconds are

parsed but silently thrown out. Because c’mon, really?

Parameters:

  • digit_string (String<0-9>)

    A string of digits

Returns:

  • (DateTime, FalseClass)

    The valid DateTime, or false (for chaining)



77
78
79
80
81
82
83
84
85
# File 'lib/date_named_file/dateish.rb', line 77

def extract_undelimited_datetime(digit_string)
  return false unless digit_string?(digit_string)
  m = /\A(\d{4})(\d{2})(\d{2})(\d{2})?(\d{2})?(\d{2})?\d*\Z/.match(digit_string)
  if m
    datetime_from_parts(m[1..-1].compact)
  else
    false
  end
end

#extract_unix_timestamp(digit_string) ⇒ DateTime, FalseClass

Any string that is (a) exactly 10 digits, and (b) starts with ‘1’ will be considered a unix timestamp and treated as such LIMITATION: Only good back to Sept 2001

Parameters:

  • digit_string (String<0-9>)

    A string of digits, should be a unix timestamp

Returns:

  • (DateTime, FalseClass)

    The valid DateTime, or false (for chaining)



92
93
94
95
96
97
98
# File 'lib/date_named_file/dateish.rb', line 92

def extract_unix_timestamp(digit_string)
  if looks_like_unix_timestamp?(digit_string)
    DateTime.strptime(digit_string, '%s')
  else
    false
  end
end

#extract_year(str) ⇒ Object



171
172
173
# File 'lib/date_named_file/dateish.rb', line 171

def extract_year(str)
  str[0..3]
end

#forgiving_dateify(date_ish) ⇒ DateTime

Attempt to turn big integer (turned into a string of digits), an actual string of digits, or a delimited string of digits (delimited by chars in VALID_DELIMITERS) into a DateTime.

Should handle:

* Something that response to #to_datetime, which just calls that.
* The symbols :today, :yesterday, and :tomorrow
* unix timestamp (see #extract_unix_timestamp)
* string of digits YYYYMMDD (see #extract_undelimited_datetime)
* delimited string of digits (see #extract_delimited_datetime)

Parameters:

  • date_ish (#to_datetime, Integer, String)

    The thing to try to convert

Returns:

  • (DateTime)

    Our best shot at a datetime

Raises:



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/date_named_file/dateish.rb', line 36

def forgiving_dateify(date_ish)
  return DateTime.now if date_ish == :today
  return (DateTime.now - 1) if date_ish == :yesterday
  return (DateTime.now + 1) if date_ish == :tomorrow
  if date_ish.respond_to?(:to_i) and date_ish.to_i < 0
    return DateTime.now + date_ish.to_i
  end
  if date_ish.respond_to? :to_datetime
    date_ish.to_datetime
  else
    str = date_ish.to_s
    if digit_string?(str)
      extract_from_digitstring(str) or
        raise InvalidDateFormat.new("All-digit string '#{str}' doesn't parse as date string or unix timestamp")
    else
      extract_delimited_datetime(str)
    end
  end
rescue InvalidDateFormat => e
  raise InvalidDateFormat.new("Can't turn '#{date_ish}' into a date-time: #{e.message}")
end

#looks_like_unix_timestamp?(digit_string) ⇒ Boolean

Is this plausible a modern unix timestamp? Valid back to 2001

Parameters:

  • digit_string (String)

Returns:

  • (Boolean)


103
104
105
106
107
# File 'lib/date_named_file/dateish.rb', line 103

def looks_like_unix_timestamp?(digit_string)
  digit_string?(digit_string) and
    digit_string.size == 10 and
    /\A1[0-9]/.match(digit_string[0..1])
end

#perform_simple_transforms(str) ⇒ Object

Deal with d/m/yyyy



127
128
129
130
131
132
133
134
# File 'lib/date_named_file/dateish.rb', line 127

def perform_simple_transforms(str)
  slash_matcher = %r[(\d{1,2})/(\d{1,2})/(\d{4})]
  if m = slash_matcher.match(str)
    '%4d-%02d-%02d' % [m[3], m[1], m[2]]
  else
    str
  end
end

#validate_delimited_datetime!(str) ⇒ Object



167
168
169
# File 'lib/date_named_file/dateish.rb', line 167

def validate_delimited_datetime!(str)
  /\A\d{4}/.match(str) or raise InvalidDateFormat.new("'#{str}' doesn't obviously start with a year")
end

#validate_parts!(parts) ⇒ Object



151
152
153
154
155
156
157
158
159
# File 'lib/date_named_file/dateish.rb', line 151

def validate_parts!(parts)
  unless parts.all? { |p| digit_string?(p) }
    raise NonDigitsInDelimitedDate.new
  end

  unless parts.all? { |p| p.size == 2 }
    raise NonTwoDigitDateParts.new
  end
end