lesli_date 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/Rakefile +3 -3
- data/docs/about/configuration.md +100 -0
- data/docs/about/development.md +131 -0
- data/docs/about/installation.md +48 -0
- data/docs/api/database.md +89 -0
- data/docs/api/examples.md +113 -0
- data/docs/api/formatter.md +104 -0
- data/docs/api/index +6 -0
- data/docs/api/overview.md +26 -0
- data/docs/api/parsing.md +101 -0
- data/docs/images/date-logo.svg +1 -0
- data/docs/navigation +2 -0
- data/lib/lesli_date/errors.rb +8 -0
- data/lib/lesli_date/formatter.rb +99 -75
- data/lib/lesli_date/parser.rb +61 -0
- data/lib/lesli_date/version.rb +2 -2
- data/readme.md +196 -39
- metadata +87 -7
data/docs/api/parsing.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Parsing rules
|
|
2
|
+
|
|
3
|
+
LesliDate uses Ruby's date parsers and Rails timezone conversion. It does not
|
|
4
|
+
compile input patterns or maintain its own validation rules for every field.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
LesliDate::Formatter.new(value, format = nil, input_zone: "UTC", zone: nil, locale: nil)
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Input values
|
|
11
|
+
|
|
12
|
+
| Input | Behavior | `.get` result |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| No argument | Current instant | `ActiveSupport::TimeWithZone` |
|
|
15
|
+
| `Time`, `TimeWithZone`, `DateTime` | Preserve the instant and apply the presentation zone | `ActiveSupport::TimeWithZone` |
|
|
16
|
+
| `Date` | Preserve the calendar date | `Date` |
|
|
17
|
+
| String without a pattern | Use `DateTime.parse` | `Date` or `ActiveSupport::TimeWithZone` |
|
|
18
|
+
| String with a pattern | Use `DateTime.strptime` | `Date` or `ActiveSupport::TimeWithZone` |
|
|
19
|
+
| Other types, including explicit `nil` | Reject with `LesliDate::ParseError` | No value |
|
|
20
|
+
|
|
21
|
+
The native parser's metadata distinguishes a calendar date from an instant.
|
|
22
|
+
Calendar dates support `.date`, `.date_words`, and `.date_words_day`. Time
|
|
23
|
+
selectors on dates raise `LesliDate::InvalidFormat` instead of inventing an hour.
|
|
24
|
+
Selecting `.date` on an instant preserves its full underlying value in `.get`.
|
|
25
|
+
|
|
26
|
+
## Native input patterns
|
|
27
|
+
|
|
28
|
+
The second positional argument is a Ruby `strptime` pattern. It supports native
|
|
29
|
+
directives, including compound directives, month names, offsets and fractions:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
LesliDate::Formatter.new("22/10/2026", "%d/%m/%Y").get
|
|
33
|
+
# => Date.new(2026, 10, 22)
|
|
34
|
+
|
|
35
|
+
LesliDate::Formatter.new("22 October 2026", "%d %B %Y").date.to_s
|
|
36
|
+
LesliDate::Formatter.new("2026-10-22 14:30:45", "%F %T").date_time.to_s
|
|
37
|
+
LesliDate::Formatter.new("2026-10-22T14:30:45.123Z").date_time.to_s
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
When a pattern is supplied, LesliDate calls `strptime` directly and does not
|
|
41
|
+
retry with `parse`. An unparseable value or a mismatch raises `ParseError` with
|
|
42
|
+
the native exception as its cause.
|
|
43
|
+
|
|
44
|
+
Accepted syntax, missing-component defaults, precision, calendar conventions,
|
|
45
|
+
and tolerance follow Ruby. For example, native parsing may accept partial dates
|
|
46
|
+
or a valid prefix with trailing text. LesliDate does not add complete-input
|
|
47
|
+
validation, a directive allowlist, year bounds, encoding conversion, or manual
|
|
48
|
+
range checks. Patterns apply to string inputs; temporal objects are used directly.
|
|
49
|
+
|
|
50
|
+
## UTC input and configured output
|
|
51
|
+
|
|
52
|
+
Lesli stores database timestamps in UTC. The initializer's
|
|
53
|
+
`Lesli.config.datetime[:time_zone]` controls presentation. Normal Lesli calls
|
|
54
|
+
need no zone options:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
# With America/Guatemala and output format %d/%m/%Y %H:%M:
|
|
58
|
+
LesliDate::Formatter.new("2026-10-22 14:30:00").date_time.to_s
|
|
59
|
+
# => "22/10/2026 08:30"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For strings containing a time but no offset, `input_zone` defaults to UTC.
|
|
63
|
+
Strings with an explicit offset already determine an instant. `zone` overrides
|
|
64
|
+
the configured presentation zone for one formatter. Dates do not undergo zone
|
|
65
|
+
conversion.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
# An external source supplies local Guatemala time rather than UTC.
|
|
69
|
+
LesliDate::Formatter.new(
|
|
70
|
+
"22/10/2026 14:30",
|
|
71
|
+
"%d/%m/%Y %H:%M",
|
|
72
|
+
input_zone: "America/Guatemala",
|
|
73
|
+
zone: "UTC"
|
|
74
|
+
).date_time.to_s
|
|
75
|
+
# => "22.10.2026 20:30", with output format %d.%m.%Y %H:%M
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Zone names use `ActiveSupport::TimeZone`. Local times, including clock-change
|
|
79
|
+
gaps and overlaps, follow Rails' native `TimeZone#local` behavior. LesliDate does
|
|
80
|
+
not implement a separate daylight-saving validation policy.
|
|
81
|
+
|
|
82
|
+
## Exceptions
|
|
83
|
+
|
|
84
|
+
| Error | Meaning |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `LesliDate::ParseError` | Unsupported input type or an exception from native parsing |
|
|
87
|
+
| `LesliDate::InvalidTimeZone` | Missing or unknown zone name; an unsupported SQLite output zone when generating SQL |
|
|
88
|
+
| `LesliDate::InvalidFormat` | Invalid output configuration, a time selector applied to a calendar date, or an unsupported PostgreSQL SQL-format directive |
|
|
89
|
+
|
|
90
|
+
All inherit from `LesliDate::Error < ArgumentError`.
|
|
91
|
+
I18n locale/translation failures keep their native exception behavior. SQL
|
|
92
|
+
execution errors come from ActiveRecord; unsupported SQLite format directives
|
|
93
|
+
return native SQL `NULL` rather than a LesliDate exception.
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
begin
|
|
97
|
+
value = LesliDate::Formatter.new(params[:date], "%d/%m/%Y").get
|
|
98
|
+
rescue LesliDate::ParseError
|
|
99
|
+
# Present the application's validation message.
|
|
100
|
+
end
|
|
101
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="#0c2856" viewBox="0 0 100 100"><desc>A formatted calendar paired with a clock.</desc><rect width="61" height="67" x="8" y="19" rx="7"/><rect width="49" height="43" x="14" y="35" fill="#d8e1ef" rx="3"/><rect width="8" height="18" x="20" y="12" rx="4"/><rect width="8" height="18" x="48" y="12" rx="4"/><rect width="9" height="8" x="20" y="43" rx="2"/><rect width="9" height="8" x="34" y="43" rx="2"/><rect width="9" height="8" x="48" y="43" rx="2"/><rect width="9" height="8" x="20" y="57" rx="2"/><rect width="9" height="8" x="34" y="57" rx="2"/><circle cx="73" cy="68" r="21"/><circle cx="73" cy="68" r="14" fill="#d8e1ef"/><path d="M70 56h6v11l8 5-3 5-11-7Z"/></svg>
|
data/docs/navigation
ADDED
data/lib/lesli_date/formatter.rb
CHANGED
|
@@ -32,14 +32,34 @@ Building a better future, one line of code at a time.
|
|
|
32
32
|
|
|
33
33
|
# maybe move this to a "LDate" gem?
|
|
34
34
|
|
|
35
|
+
require "lesli_date/parser"
|
|
36
|
+
require "active_record"
|
|
37
|
+
require "i18n"
|
|
38
|
+
|
|
35
39
|
module LesliDate
|
|
36
40
|
class Formatter
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
DEFAULT_FORMATS = {
|
|
43
|
+
:date => "%d.%m.%Y",
|
|
44
|
+
:time => "%H:%M",
|
|
45
|
+
:date_time => "%d.%m.%Y %H:%M",
|
|
46
|
+
:date_words => "%B %d, %Y", # date in words including day's name
|
|
47
|
+
:date_words_day => "%A, %B %d, %Y", # date in words including day's name
|
|
48
|
+
:date_time_words => "%B %d, %Y, %H:%M", # 24 hours datetime in words format (default)
|
|
49
|
+
:date_time_words_pm => "%B %d, %Y, %I:%M %p", # 12 hours datetime in words format
|
|
50
|
+
:date_time_words_day => "%A, %B %d, %Y, %H:%M", # 24 hours datetime in words format (default)
|
|
51
|
+
}.freeze
|
|
52
|
+
private_constant :DEFAULT_FORMATS
|
|
53
|
+
|
|
54
|
+
def initialize(datetime = Time.current, format = nil, input_zone: "UTC", zone: nil, locale: nil)
|
|
39
55
|
|
|
40
56
|
# NOTE: user should be able to change this through settings table
|
|
41
57
|
# get initial datetime configuration
|
|
42
|
-
config = Lesli.config.datetime
|
|
58
|
+
config = Lesli.config.datetime || {}
|
|
59
|
+
configured_zone = config[:time_zone]
|
|
60
|
+
output_zone = zone.nil? ? configured_zone : zone
|
|
61
|
+
# Lesli stores timestamps in UTC; the configured zone controls presentation.
|
|
62
|
+
@parser = Parser.new(input_zone: input_zone, zone: output_zone)
|
|
43
63
|
|
|
44
64
|
|
|
45
65
|
# NOTE: Do not modify settings here,
|
|
@@ -63,19 +83,11 @@ module LesliDate
|
|
|
63
83
|
# }
|
|
64
84
|
|
|
65
85
|
@settings = {
|
|
66
|
-
:time_zone =>
|
|
67
|
-
:start_week_on => config[:start_week_on],
|
|
68
|
-
:format =>
|
|
69
|
-
:date => config[:formats][:date] || "%d.%m.%Y",
|
|
70
|
-
:time => "%H:%M",
|
|
71
|
-
:date_time => config[:formats][:date_time] || "%d.%m.%Y %H:%M",
|
|
72
|
-
:date_words => "%B %d, %Y", # date in words including day's name
|
|
73
|
-
:date_words_day => "%A, %B %d, %Y", # date in words including day's name
|
|
74
|
-
:date_time_words => "%B %d, %Y, %H:%M", # 24 hours datetime in words format (default)
|
|
75
|
-
:date_time_words_pm => "%B %d, %Y, %I:%M %p", # 12 hours datetime in words format
|
|
76
|
-
:date_time_words_day => "%A, %B %d, %Y, %H:%M", # 24 hours datetime in words format (default)
|
|
77
|
-
}
|
|
86
|
+
:time_zone => @parser.output_zone.name,
|
|
87
|
+
:start_week_on => config[:start_week_on] || "monday",
|
|
88
|
+
:format => configured_formats(config[:formats])
|
|
78
89
|
}
|
|
90
|
+
@locale = locale
|
|
79
91
|
|
|
80
92
|
|
|
81
93
|
# default date format
|
|
@@ -83,25 +95,16 @@ module LesliDate
|
|
|
83
95
|
|
|
84
96
|
|
|
85
97
|
# get a valid timezone
|
|
86
|
-
@zone =
|
|
98
|
+
@zone = @parser.output_zone
|
|
87
99
|
|
|
88
100
|
|
|
89
101
|
# get datetime object from user params
|
|
90
|
-
@datetime = parse_initial_datetime(datetime, format)
|
|
102
|
+
@datetime = parse_initial_datetime(datetime, format)
|
|
91
103
|
|
|
92
104
|
end
|
|
93
105
|
|
|
94
106
|
# Dynamically define formatting methods like `date`, `time`, etc.
|
|
95
|
-
|
|
96
|
-
:date,
|
|
97
|
-
:time,
|
|
98
|
-
:date_time,
|
|
99
|
-
:date_words,
|
|
100
|
-
:date_words_day,
|
|
101
|
-
:date_time_words,
|
|
102
|
-
:date_time_words_pm,
|
|
103
|
-
:date_time_words_day
|
|
104
|
-
].each do |key|
|
|
107
|
+
DEFAULT_FORMATS.each_key do |key|
|
|
105
108
|
define_method(key) do
|
|
106
109
|
set_format(key)
|
|
107
110
|
self
|
|
@@ -110,47 +113,43 @@ module LesliDate
|
|
|
110
113
|
|
|
111
114
|
# return query string to get timestamps columns from database
|
|
112
115
|
def db_timestamps table=""
|
|
113
|
-
|
|
114
|
-
# avoid ambiguous columns
|
|
115
|
-
table = table.concat(".") if table != ""
|
|
116
|
-
|
|
117
|
-
# get right format for dates
|
|
118
|
-
format = self.db_format
|
|
119
|
-
|
|
120
|
-
# compatibility for SQLite
|
|
121
|
-
if ActiveRecord::Base.connection.adapter_name == "SQLite"
|
|
122
|
-
offset = ActiveSupport::TimeZone[@settings[:time_zone]].utc_offset / 3600
|
|
123
|
-
return "strftime('#{format}', #{table}created_at, '#{offset} hours') as created_at_string"
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
"TO_CHAR(#{table}created_at at time zone 'utc' at time zone '#{@settings[:time_zone]}', '#{format}') as created_at_date, TO_CHAR(#{table}updated_at at time zone 'utc' at time zone '#{@settings[:time_zone]}', '#{format}') as updated_at_date"
|
|
127
|
-
|
|
116
|
+
[:created_at, :updated_at].map { |column| db_column(column, table) }.join(", ")
|
|
128
117
|
end
|
|
129
118
|
|
|
130
119
|
# return query string to get a datetime column from database
|
|
131
120
|
def db_column column, table="", as:nil
|
|
132
|
-
|
|
133
|
-
as = "#{column}_string" unless as
|
|
121
|
+
connection = ActiveRecord::Base.connection
|
|
134
122
|
|
|
135
123
|
# avoid ambiguous columns
|
|
136
|
-
|
|
124
|
+
identifier = connection.quote_column_name(column.to_s)
|
|
125
|
+
identifier = "#{connection.quote_table_name(table.to_s)}.#{identifier}" unless table.to_s.empty?
|
|
137
126
|
|
|
138
127
|
# get right format for dates
|
|
139
|
-
format =
|
|
128
|
+
format = connection.quote(db_format)
|
|
140
129
|
|
|
141
130
|
# compatibility for SQLite
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
131
|
+
case connection.adapter_name
|
|
132
|
+
when "SQLite"
|
|
133
|
+
# SQLite supports fixed offsets, including fractional hours, but not IANA rules.
|
|
134
|
+
period = @zone.tzinfo.period_for_utc(Time.current.utc)
|
|
135
|
+
raise InvalidTimeZone, "SQLite requires a fixed output offset; use UTC or format #{@zone.name} in Ruby" if period.ends_at
|
|
136
|
+
|
|
137
|
+
offset = connection.quote("#{period.observed_utc_offset} seconds")
|
|
138
|
+
sql = "strftime(#{format}, #{identifier}, #{offset})"
|
|
139
|
+
when "PostgreSQL"
|
|
140
|
+
zone = connection.quote(@zone.tzinfo.identifier)
|
|
141
|
+
sql = "TO_CHAR(#{identifier} AT TIME ZONE 'UTC' AT TIME ZONE #{zone}, #{format})"
|
|
142
|
+
else
|
|
143
|
+
raise NotImplementedError, "LesliDate SQL does not support #{connection.adapter_name}"
|
|
145
144
|
end
|
|
146
145
|
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
return sql if as == false
|
|
147
|
+
"#{sql} AS #{connection.quote_column_name((as || "#{column}_string").to_s)}"
|
|
149
148
|
end
|
|
150
149
|
|
|
151
150
|
# convert a datetime object to string representation using defined format
|
|
152
151
|
def to_s
|
|
153
|
-
|
|
152
|
+
I18n.localize(@datetime, format: @format, locale: @locale)
|
|
154
153
|
end
|
|
155
154
|
|
|
156
155
|
def get
|
|
@@ -159,43 +158,68 @@ module LesliDate
|
|
|
159
158
|
|
|
160
159
|
private
|
|
161
160
|
|
|
161
|
+
def configured_formats(formats)
|
|
162
|
+
formats = {} if formats.nil?
|
|
163
|
+
raise InvalidFormat, "datetime[:formats] must be a hash" unless formats.is_a?(Hash)
|
|
162
164
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
DEFAULT_FORMATS.merge(formats.compact).to_h do |name, pattern|
|
|
166
|
+
unless pattern.is_a?(String) && pattern.valid_encoding? && !pattern.strip.empty?
|
|
167
|
+
raise InvalidFormat, "Format #{name} must be a nonempty string"
|
|
168
|
+
end
|
|
167
169
|
|
|
168
|
-
|
|
169
|
-
return ::DateTime.iso8601(datetime)
|
|
170
|
+
[name, validated_format(pattern)]
|
|
170
171
|
end
|
|
172
|
+
end
|
|
171
173
|
|
|
172
|
-
|
|
174
|
+
def validated_format(pattern)
|
|
175
|
+
Time.current.strftime(pattern)
|
|
176
|
+
pattern.dup.freeze
|
|
177
|
+
rescue ArgumentError => error
|
|
178
|
+
raise InvalidFormat, error.message
|
|
173
179
|
end
|
|
174
180
|
|
|
175
181
|
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
# get datetime object or string datetime and return a datetime object
|
|
183
|
+
def parse_initial_datetime datetime, format
|
|
184
|
+
# Calendar dates remain Dates; the parser converts only instants.
|
|
185
|
+
@parser.parse(datetime, format)
|
|
178
186
|
end
|
|
179
187
|
|
|
180
188
|
|
|
181
|
-
def
|
|
182
|
-
format
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return format if ActiveRecord::Base.connection.adapter_name == "SQLite"
|
|
189
|
+
def set_format format
|
|
190
|
+
if @datetime.is_a?(::Date) && ![:date, :date_words, :date_words_day].include?(format || :date)
|
|
191
|
+
raise InvalidFormat, "A calendar date cannot be formatted with a time selector"
|
|
192
|
+
end
|
|
186
193
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
format = format.gsub("%m", "MM")
|
|
190
|
-
format = format.gsub("%d", "DD")
|
|
194
|
+
@format = @settings[:format][format || :date]
|
|
195
|
+
end
|
|
191
196
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
format
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
def db_format
|
|
198
|
+
# SQLite format dates are the same of ruby format
|
|
199
|
+
return @format if ActiveRecord::Base.connection.adapter_name == "SQLite"
|
|
200
|
+
|
|
201
|
+
formats = {
|
|
202
|
+
# Convert Ruby to postgresql date format
|
|
203
|
+
"%Y" => "YYYY", "%y" => "YY", "%m" => "MM", "%d" => "DD",
|
|
204
|
+
"%B" => "FMMonth", "%b" => "FMMon", "%A" => "FMDay", "%a" => "FMDy",
|
|
205
|
+
"%j" => "DDD", "%F" => "YYYY-MM-DD",
|
|
206
|
+
# Convert Ruby to postgresql time format
|
|
207
|
+
"%H" => "HH24", # 24 hours format
|
|
208
|
+
"%I" => "HH12", # 12 hours format
|
|
209
|
+
"%p" => "AM", # Meridian indicator (AM/PM)
|
|
210
|
+
"%P" => "am", "%M" => "MI", "%S" => "SS",
|
|
211
|
+
"%R" => "HH24:MI", "%T" => "HH24:MI:SS", "%%" => '"%"'
|
|
212
|
+
}
|
|
197
213
|
|
|
198
|
-
format
|
|
214
|
+
@format.gsub(/%.|[^%]+|%/) do |part|
|
|
215
|
+
if part.start_with?("%")
|
|
216
|
+
formats.fetch(part) { raise InvalidFormat, "PostgreSQL does not support #{part.inspect}" }
|
|
217
|
+
else
|
|
218
|
+
# Quoted text stays literal inside PostgreSQL's format template.
|
|
219
|
+
'"' + part.gsub(/["\\]/) { |character| "\\#{character}" } + '"'
|
|
220
|
+
end
|
|
221
|
+
end
|
|
199
222
|
end
|
|
223
|
+
|
|
200
224
|
end
|
|
201
225
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support"
|
|
4
|
+
require "active_support/time"
|
|
5
|
+
require "lesli_date/errors"
|
|
6
|
+
|
|
7
|
+
module LesliDate
|
|
8
|
+
# Calendar dates remain Dates; only instants undergo timezone conversion.
|
|
9
|
+
class Parser
|
|
10
|
+
attr_reader :output_zone
|
|
11
|
+
|
|
12
|
+
def initialize(input_zone:, zone:)
|
|
13
|
+
@output_zone = resolve_zone(zone)
|
|
14
|
+
@input_zone = resolve_zone(input_zone)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse(value, format = nil)
|
|
18
|
+
case value
|
|
19
|
+
when String
|
|
20
|
+
parse_string(value, format)
|
|
21
|
+
when Time, ActiveSupport::TimeWithZone
|
|
22
|
+
value.in_time_zone(@output_zone)
|
|
23
|
+
when DateTime
|
|
24
|
+
value.new_start(Date::GREGORIAN).in_time_zone(@output_zone)
|
|
25
|
+
when Date
|
|
26
|
+
value
|
|
27
|
+
else
|
|
28
|
+
raise ParseError, "Expected a date, time or date string"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def resolve_zone(name)
|
|
35
|
+
zone = ActiveSupport::TimeZone[name] if name.is_a?(String)
|
|
36
|
+
zone || raise(InvalidTimeZone, "Unknown time zone: #{name.inspect}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def parse_string(value, format)
|
|
40
|
+
value = value.strip
|
|
41
|
+
datetime = format ? DateTime.strptime(value, format) : DateTime.parse(value)
|
|
42
|
+
parts = format ? Date._strptime(value, format) : Date._parse(value)
|
|
43
|
+
|
|
44
|
+
# Native parsing metadata distinguishes a date from a time or explicit offset.
|
|
45
|
+
return datetime.to_date if (parts.keys & [:hour, :min, :sec, :seconds, :offset]).empty?
|
|
46
|
+
|
|
47
|
+
if parts[:offset] || parts[:seconds]
|
|
48
|
+
return datetime.new_start(Date::GREGORIAN).in_time_zone(@output_zone)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
@input_zone.local(
|
|
52
|
+
datetime.year, datetime.month, datetime.day,
|
|
53
|
+
datetime.hour, datetime.minute, datetime.second + datetime.sec_fraction
|
|
54
|
+
).in_time_zone(@output_zone)
|
|
55
|
+
rescue ArgumentError, TypeError => error
|
|
56
|
+
raise ParseError, error.message
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private_constant :Parser
|
|
61
|
+
end
|
data/lib/lesli_date/version.rb
CHANGED