flexitime 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7761e5fbc7cf6ddf2a963ae8a38f8ade7b38db26eb2835b23def41df09599ad9
4
- data.tar.gz: d7e5366d7d36c665f17d9695b563c894451538925efd016d5e4f0ed5ae695cab
3
+ metadata.gz: 6dfd029b07e75148e00109b01e4c6c17f09edfb2629dffe091a9003f2c1fb610
4
+ data.tar.gz: c4290263155f0291fcd2ca897075cb315f76f5a99fc20623042225cef6e3c3e7
5
5
  SHA512:
6
- metadata.gz: fe193a2473e99b72abd0d94751a491f9f5971c8861a671053afc3151c7eea04588fb20a6279d7bd2fa3d33e4b5ec8e92f82f418f4bf4ad3d2e807daa8da8415a
7
- data.tar.gz: 617842e3fd380181f280fe8ffdff7fea5bb52165596816b6ad0be9d7190bae37e3487840a28d7144f614c4d0f660ad652108d4a5cc9e8231e094cf90e2670d56
6
+ metadata.gz: c15e333e96e05f99b4dbed931f217be00a5c3832c47dc5adef7e5e76e740cf9fb2576deeece81e86e66cca10ba96c6492a97284bee49f7780c4f9a5a3bfeb039
7
+ data.tar.gz: '007581d7382a12508c861ca67b7024ce094251d33050467be371bc12ec1399a080f642a4bb0a7ba11b3ac4364a3e4eadc0f80ce0ec796e8781f9ce0f3969b0d0'
@@ -17,20 +17,85 @@ jobs:
17
17
  strategy:
18
18
  fail-fast: false # should GitHub cancel all in-progress jobs if any matrix job fails
19
19
  matrix:
20
- ruby-version:
21
- - '2.5'
22
- - '2.6'
23
- - '2.7'
24
- - '3.0'
25
- gemfile:
26
- - 'activesupport_4_0'
27
- - 'activesupport_4_1'
28
- - 'activesupport_4_2'
29
- - 'activesupport_5_0'
30
- - 'activesupport_5_1'
31
- - 'activesupport_5_2'
32
- - 'activesupport_6_0'
33
- - 'activesupport_6_1'
20
+ ruby-version: ['2.5']
21
+ gemfile: ['activesupport_4_0']
22
+ include:
23
+ - ruby-version: '2.5'
24
+ gemfile: 'activesupport_4_1'
25
+ - ruby-version: '2.5'
26
+ gemfile: 'activesupport_4_2'
27
+ - ruby-version: '2.5'
28
+ gemfile: 'activesupport_5_0'
29
+ - ruby-version: '2.5'
30
+ gemfile: 'activesupport_5_1'
31
+ - ruby-version: '2.5'
32
+ gemfile: 'activesupport_5_2'
33
+ - ruby-version: '2.5'
34
+ gemfile: 'activesupport_6_0'
35
+ - ruby-version: '2.5'
36
+ gemfile: 'activesupport_6_1'
37
+ - ruby-version: '2.6'
38
+ gemfile: 'activesupport_4_0'
39
+ - ruby-version: '2.6'
40
+ gemfile: 'activesupport_4_1'
41
+ - ruby-version: '2.6'
42
+ gemfile: 'activesupport_4_2'
43
+ - ruby-version: '2.6'
44
+ gemfile: 'activesupport_5_0'
45
+ - ruby-version: '2.6'
46
+ gemfile: 'activesupport_5_1'
47
+ - ruby-version: '2.6'
48
+ gemfile: 'activesupport_5_2'
49
+ - ruby-version: '2.6'
50
+ gemfile: 'activesupport_6_0'
51
+ - ruby-version: '2.6'
52
+ gemfile: 'activesupport_6_1'
53
+ - ruby-version: '2.7'
54
+ gemfile: 'activesupport_4_0'
55
+ - ruby-version: '2.7'
56
+ gemfile: 'activesupport_4_1'
57
+ - ruby-version: '2.7'
58
+ gemfile: 'activesupport_4_2'
59
+ - ruby-version: '2.7'
60
+ gemfile: 'activesupport_5_0'
61
+ - ruby-version: '2.7'
62
+ gemfile: 'activesupport_5_1'
63
+ - ruby-version: '2.7'
64
+ gemfile: 'activesupport_5_2'
65
+ - ruby-version: '2.7'
66
+ gemfile: 'activesupport_6_0'
67
+ - ruby-version: '2.7'
68
+ gemfile: 'activesupport_6_1'
69
+ - ruby-version: '2.7'
70
+ gemfile: 'activesupport_7_0'
71
+ - ruby-version: '3.0'
72
+ gemfile: 'activesupport_4_2'
73
+ - ruby-version: '3.0'
74
+ gemfile: 'activesupport_5_0'
75
+ - ruby-version: '3.0'
76
+ gemfile: 'activesupport_5_1'
77
+ - ruby-version: '3.0'
78
+ gemfile: 'activesupport_5_2'
79
+ - ruby-version: '3.0'
80
+ gemfile: 'activesupport_6_0'
81
+ - ruby-version: '3.0'
82
+ gemfile: 'activesupport_6_1'
83
+ - ruby-version: '3.0'
84
+ gemfile: 'activesupport_7_0'
85
+ - ruby-version: '3.1'
86
+ gemfile: 'activesupport_4_2'
87
+ - ruby-version: '3.1'
88
+ gemfile: 'activesupport_5_0'
89
+ - ruby-version: '3.1'
90
+ gemfile: 'activesupport_5_1'
91
+ - ruby-version: '3.1'
92
+ gemfile: 'activesupport_5_2'
93
+ - ruby-version: '3.1'
94
+ gemfile: 'activesupport_6_0'
95
+ - ruby-version: '3.1'
96
+ gemfile: 'activesupport_6_1'
97
+ - ruby-version: '3.1'
98
+ gemfile: 'activesupport_7_0'
34
99
 
35
100
  env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
36
101
  BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
data/Appraisals CHANGED
@@ -11,6 +11,10 @@
11
11
  # Run each appraisal in turn or a single appraisal:-
12
12
  # $ bundle exec appraisal rspec
13
13
  # $ bundle exec appraisal activesupport-6-1 rspec
14
+ appraise "activesupport-7-0" do
15
+ gem "activesupport", "~> 7.0"
16
+ end
17
+
14
18
  appraise "activesupport-6-1" do
15
19
  gem "activesupport", "~> 6.1"
16
20
  end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
- ## [Unreleased]
1
+ # Change Log
2
2
 
3
- ## [0.1.0] - 2021-11-22
3
+ ## 1.0.0 - 2022-03-17
4
4
 
5
- - Initial release
5
+ * Perform parsing using `Time.zone` ([d30b224](https://github.com/CircleSD/flexitime/commit/d30b224f40379331473f60d11f6dcc8b1875413d))
6
+ * Add `first_date_part` and `precision` arguments to `parse` method ([18ce552](https://github.com/CircleSD/flexitime/commit/18ce5528caf2f1c6d2643b610a713e7d2cd6ce75))
7
+
8
+ ## 0.1.0 - 2021-11-22
9
+
10
+ * Initial release
data/Gemfile.lock CHANGED
@@ -1,18 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- flexitime (0.1.0)
4
+ flexitime (1.0.0)
5
5
  activesupport (>= 4.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activesupport (6.1.4.1)
10
+ activesupport (7.0.2.3)
11
11
  concurrent-ruby (~> 1.0, >= 1.0.2)
12
12
  i18n (>= 1.6, < 2)
13
13
  minitest (>= 5.1)
14
14
  tzinfo (~> 2.0)
15
- zeitwerk (~> 2.3)
16
15
  appraisal (2.4.1)
17
16
  bundler
18
17
  rake
@@ -20,9 +19,10 @@ GEM
20
19
  ast (2.4.2)
21
20
  concurrent-ruby (1.1.9)
22
21
  diff-lcs (1.4.4)
23
- i18n (1.8.11)
22
+ docile (1.4.0)
23
+ i18n (1.10.0)
24
24
  concurrent-ruby (~> 1.0)
25
- minitest (5.14.4)
25
+ minitest (5.15.0)
26
26
  parallel (1.21.0)
27
27
  parser (3.0.2.0)
28
28
  ast (~> 2.4.1)
@@ -58,6 +58,12 @@ GEM
58
58
  rubocop (>= 1.7.0, < 2.0)
59
59
  rubocop-ast (>= 0.4.0)
60
60
  ruby-progressbar (1.11.0)
61
+ simplecov (0.21.2)
62
+ docile (~> 1.1)
63
+ simplecov-html (~> 0.11)
64
+ simplecov_json_formatter (~> 0.1)
65
+ simplecov-html (0.12.3)
66
+ simplecov_json_formatter (0.1.4)
61
67
  standard (1.4.0)
62
68
  rubocop (= 1.22.3)
63
69
  rubocop-performance (= 1.11.5)
@@ -65,7 +71,6 @@ GEM
65
71
  tzinfo (2.0.4)
66
72
  concurrent-ruby (~> 1.0)
67
73
  unicode-display_width (2.1.0)
68
- zeitwerk (2.5.1)
69
74
 
70
75
  PLATFORMS
71
76
  arm64-darwin-21
@@ -75,6 +80,7 @@ DEPENDENCIES
75
80
  flexitime!
76
81
  rake (~> 13.0)
77
82
  rspec (~> 3.0)
83
+ simplecov
78
84
  standard
79
85
 
80
86
  BUNDLED WITH
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Flexitime
2
2
 
3
- Flexitime is a Ruby date/time string parser with the intended purpose of converting a string value received from a UI or API into a Time object. It offers the flexibility of deciphering date/time strings in the most common formats with the ability to self-determine the expected order of the date parts when using [rails-i18n](https://github.com/svenfuchs/rails-i18n) or set this via a configuration option; configure the parser to return Time objects either in the system local time or for a specified [time zone](https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html); and set a desired precision for the Time object.
3
+ Flexitime is a Ruby date/time string parser with the intended purpose of converting a string value received from a UI or API into an [ActiveSupport::TimeWithZone](https://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html) object. It offers the flexibility of deciphering date/time strings in the most common formats with the ability to self-determine the expected order of the date parts when using [rails-i18n](https://github.com/svenfuchs/rails-i18n). The date order and a desired precision for the time object can also be set via configuration options or method arguments.
4
4
 
5
- The gem was born of the need to parse date, datetime & time strings in a multi-user environment supporting different locales and time zones. Depending upon the user's locale the UI would return date/time strings in different formats and in different orders (day/month/year or month/day/year). This variation in the ordering of the day and month parts proved to be the main catalyst to finding or creating a date/time parser. The resultant Time object needed to be created in the user's time zone and additionally the system stored times only to a minute precision. Flexitime was created to provide a simple yet flexible parser to meet these needs.
5
+ The gem was born of the need to parse date, datetime & time strings in a multi-user environment supporting different locales and time zones. Depending upon the user's locale the UI would return date/time strings in different formats and in different orders (day/month/year or month/day/year). This variation in the ordering of the day and month parts proved to be the main catalyst to finding or creating a date/time parser. The resultant time object needed to be created in the user's time zone and additionally the system stored times only to a minute precision. Flexitime was created to provide a simple yet flexible parser to meet these needs.
6
6
 
7
7
  ![Build Status](https://github.com/CircleSD/flexitime/actions/workflows/ci.yml/badge.svg?branch=main)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -29,22 +29,107 @@ gem install flexitime
29
29
 
30
30
  ## Usage
31
31
 
32
- The Flexitime `parse` method accepts a string argument or an object that implements the `to_str` method to denote that it behaves like a string. When the string is recognised as a valid date, datetime or time it returns a Time object otherwise it returns `nil` if any of the date/time parts are invalid or the string does not match a recognised format.
32
+ The Flexitime `parse` method accepts a string argument or an object that implements the `to_str` method (to denote that it behaves like a string). When the string is recognised as a valid date, datetime or time it returns an [ActiveSupport::TimeWithZone](https://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html) object. If any of the date/time parts are invalid or the string does not match a recognised format the method returns `nil`.
33
+
34
+ As Flexitime uses the [TimeZone](https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html) class, `Time.zone` should be set before using the `parse` method. If `Time.zone` is `nil` Flexitime will set the zone to UTC.
33
35
 
34
36
  ```ruby
35
- Flexitime.parse("23/08/2021") # => 2021-08-23 00:00:00 +0100
36
- Flexitime.parse("23/08/2021 08:00") # => 2021-08-23 08:00:00 +0100
37
- Flexitime.parse("08:00") # => 2021-11-21 08:00:00 +0000
37
+ Time.zone = "London"
38
+ Flexitime.parse("23/08/2021") # => Mon, 23 Aug 2021 00:00:00.000000000 BST +01:00
39
+ Flexitime.parse("23/08/2021 08:00") # => Mon, 23 Aug 2021 08:00:00.000000000 BST +01:00
40
+ Flexitime.parse("08:00") # => Mon, 14 Mar 2022 08:00:00.000000000 GMT +00:00
38
41
  ```
39
42
 
40
43
  ```ruby
44
+ Time.zone = "London"
41
45
  Flexitime.parse("31/02/2021 08:00") # => nil
42
46
  Flexitime.parse("computer says no") # => nil
43
47
  ```
44
48
 
49
+ ### First Date Part
50
+
51
+ The `first_date_part` option is used to denote the first part of the date within the string, either `:day` or `:month`. When using the [rails-I18n gem](https://github.com/svenfuchs/rails-i18n) if the first element of the "date.order" translation is :day or :month that will be used. Alternatively the option can be set manually via a configuration option or a `parse` argument. If the option has not been set then the default of `:day` is used.
52
+
53
+ ```ruby
54
+ Time.zone = "London"
55
+ I18n.locale = :"en-GB"
56
+ I18n.t("date.order") # => [:day, :month, :year]
57
+ Flexitime.parse("01/06/2021") # => Tue, 01 Jun 2021 00:00:00.000000000 BST +01:00
58
+
59
+ I18n.locale = :"en-US"
60
+ I18n.t("date.order") # => [:month, :day, :year]
61
+ Flexitime.parse("01/06/2021") # => Wed, 06 Jan 2021 00:00:00.000000000 GMT +00:00
62
+ ```
63
+
64
+ ```ruby
65
+ Time.zone = "London"
66
+ Flexitime.first_date_part = :day
67
+ Flexitime.parse("01/06/2021") # => Tue, 01 Jun 2021 00:00:00.000000000 BST +01:00
68
+
69
+ Flexitime.first_date_part = :month
70
+ Flexitime.parse("01/06/2021") # => Wed, 06 Jan 2021 00:00:00.000000000 GMT +00:00
71
+ ```
72
+
73
+ ```ruby
74
+ Time.zone = "London"
75
+ Flexitime.parse("01/06/2021", first_date_part: :day) # => Tue, 01 Jun 2021 00:00:00.000000000 BST +01:00
76
+ Flexitime.parse("01/06/2021", first_date_part: :month) # => Wed, 06 Jan 2021 00:00:00.000000000 GMT +00:00
77
+ ```
78
+
79
+ Regardless of the `first_date_part` value, Flexitime will always attempt to parse a string starting with a 4 digit year, deciphering the format as year/month/day.
80
+
81
+ ```ruby
82
+ Time.zone = "London"
83
+ Flexitime.parse("2021-11-12 08:15") # => Fri, 12 Nov 2021 08:15:00.000000000 GMT +00:00
84
+ ```
85
+
86
+ ### Precision
87
+
88
+ The `precision` option denotes the desired precision for the returned time objects. This defaults to minute (`:min`) meaning that the time object will be returned without seconds. The option can be set via a configuration option or a `parse` argument and the accepted values are `:day`, `:hour`, `:min`, `:sec` or `:usec`
89
+
90
+ ```ruby
91
+ Time.zone = "London"
92
+ Flexitime.precision = :day
93
+ Flexitime.parse("2022-12-01T18:30:45.036711Z") # => Thu, 01 Dec 2022 00:00:00.000000000 GMT +00:00
94
+ Flexitime.precision = :hour
95
+ Flexitime.parse("2022-12-01T18:30:45.036711Z") # => Thu, 01 Dec 2022 18:00:00.000000000 GMT +00:00
96
+ Flexitime.precision = :min
97
+ Flexitime.parse("2022-12-01T18:30:45.036711Z") # => Thu, 01 Dec 2022 18:30:00.000000000 GMT +00:00
98
+ Flexitime.precision = :sec
99
+ Flexitime.parse("2022-12-01T18:30:45.036711Z") # => Thu, 01 Dec 2022 18:30:45.000000000 GMT +00:00
100
+ Flexitime.precision = :usec
101
+ Flexitime.parse("2022-12-01T18:30:45.036711Z") # => Thu, 01 Dec 2022 18:30:45.036711000 GMT +00:00
102
+
103
+ Flexitime.parse("2022-12-01T18:30:45.036711Z", precision: :day) # => Thu, 01 Dec 2022 00:00:00.000000000 GMT +00:00
104
+ Flexitime.parse("2022-12-01T18:30:45.036711Z", precision: :hour) # => Thu, 01 Dec 2022 18:00:00.000000000 GMT +00:00
105
+ Flexitime.parse("2022-12-01T18:30:45.036711Z", precision: :min) # => Thu, 01 Dec 2022 18:30:00.000000000 GMT +00:00
106
+ Flexitime.parse("2022-12-01T18:30:45.036711Z", precision: :sec) # => Thu, 01 Dec 2022 18:30:45.000000000 GMT +00:00
107
+ Flexitime.parse("2022-12-01T18:30:45.036711Z", precision: :usec) # => Thu, 01 Dec 2022 18:30:45.036711000 GMT +00:00
108
+ ```
109
+
110
+ ### Ambiguous Year Future Bias
111
+
112
+ The `ambiguous_year_future_bias` configuration option is used to determine the century when parsing a string containing a 2 digit year and defaults to 50.
113
+
114
+ With a bias of 50, when the current year is 2020 the time object's year is set from within a range of >= 1970 and <= 2069
115
+
116
+ With a bias of 20, when the current year is 2020 the time object's year is set from within a range of >= 2000 and <= 2099
117
+
118
+ ```ruby
119
+ Time.zone = "London"
120
+ Flexitime.parse("01/08/00").year # => 2000
121
+ Flexitime.parse("01/08/71").year # => 2071
122
+ Flexitime.parse("01/08/72").year # => 1972
123
+ Flexitime.parse("01/08/99").year # => 1999
124
+ ```
125
+
126
+ ### Thread Safety
127
+
128
+ The Flexitime `configuration` instance is stored in the currently executing thread as a thread-local variable in order to avoid conflicts with concurrent requests and make the variable threadsafe.
129
+
45
130
  ## Formats
46
131
 
47
- Flexitime uses regular expressions to match the string to the most common date & time formats and uses the matched parts to create a Time object.
132
+ Flexitime uses regular expressions to match the string to the most common date & time formats and uses the matched parts to create a time object.
48
133
 
49
134
  Dates separated by either forward slashes `/` hyphens `-` or periods `.`; with 1 or 2 digit days and months; with 2 or 4 digit years; and with day/month or year first (always a 4 digit year).
50
135
 
@@ -87,89 +172,14 @@ Also times in the ISO 8601 Zulu format with between 1 and 6 digit milliseconds
87
172
  2021-08-01T08:15:30.144515Z
88
173
  ```
89
174
 
90
- If the string does not match any of the regular expressions then Flexitime will attempt to parse the string using the time class, so you lose nothing from using Flexitime and it will still return a Time object for a string containing, for example, an offset or words.
175
+ If the string does not match any of the regular expressions then Flexitime will attempt to parse the string using the TimeZone class, so you lose nothing from using Flexitime and it will still return a time object for a string containing, for example, an offset or words.
91
176
 
92
177
  ```ruby
93
- Flexitime.parse("2021-01-02T09:00:00+02:00") # => 2021-01-02 09:00:00 +0200
94
- Flexitime.parse("2nd January 2021") # => 2021-01-02 00:00:00 +0000
178
+ Time.zone = "Kyiv"
179
+ Flexitime.parse("2022-02-24T09:00:00+02:00") # => Thu, 24 Feb 2022 09:00:00.000000000 EET +02:00
180
+ Flexitime.parse("2nd January 2021") # => Sat, 02 Jan 2021 00:00:00.000000000 EET +02:00
95
181
  ```
96
182
 
97
- ## Configuration
98
-
99
- ### Time Class
100
-
101
- The Flexitime `time_class` configuration option is used to create Time objects and defaults to the `Time` class which will use the system local time zone. The `time_class` can be set to an ActiveSupport [TimeZone](https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html) to create Time objects using the specified time zone.
102
-
103
- ```ruby
104
- Time.zone = "Europe/London"
105
- Flexitime.time_class = Time.zone
106
- Flexitime.parse("01/06/2021 17:00") # => Tue, 01 Jun 2021 17:00:00.000000000 BST +01:00
107
- ```
108
-
109
- ### First Date Part
110
-
111
- The Flexitime `first_date_part` configuration option is used to denote the first part of the date within the string, either `:day` or `:month`. When using the [rails-I18n gem](https://github.com/svenfuchs/rails-i18n) if the first element of the "date.order" translation is :day or :month that will be used. Alternatively the option can be set manually or finally the default of `:day` will be used.
112
-
113
- ```ruby
114
- I18n.locale = :"en-GB"
115
- I18n.t("date.order") # => [:day, :month, :year]
116
- Flexitime.parse("01/06/2021") # => 2021-06-01 00:00:00 +0100
117
-
118
- I18n.locale = :"en-US"
119
- I18n.t("date.order") # => [:month, :day, :year]
120
- Flexitime.parse("01/06/2021") # => 2021-01-06 00:00:00 +0100
121
- ```
122
-
123
- ```ruby
124
- Flexitime.first_date_part = :day
125
- Flexitime.parse("01/06/2021") # => 2021-06-01 00:00:00 +0100
126
-
127
- Flexitime.first_date_part = :month
128
- Flexitime.parse("01/06/2021") # => 2021-01-06 00:00:00 +0100
129
- ```
130
-
131
- Regardless of the `first_date_part` value, Flexitime will always attempt to parse a string starting with a 4 digit year, deciphering the format as year/month/day.
132
-
133
- ```ruby
134
- Flexitime.parse("2021-11-12 08:15") # => 2021-11-12 08:15:00 +0000
135
- ```
136
-
137
- ### Precision
138
-
139
- The Flexitime `precision` configuration option denotes the desired precision for the returned Time objects. This defaults to minute (`:min`) meaning that the Time object will be returned without seconds. The accepted values are `:day`, `:hour`, `:min`, `:sec` or `:usec`
140
-
141
- ```ruby
142
- Flexitime.precision = :day
143
- Flexitime.parse("2022-12-01T18:30:45.036711Z") # => 2022-12-01 00:00:00 +0000
144
- Flexitime.precision = :hour
145
- Flexitime.parse("2022-12-01T18:30:45.036711Z") # => 2022-12-01 18:00:00 +0000
146
- Flexitime.precision = :min
147
- Flexitime.parse("2022-12-01T18:30:45.036711Z") # => 2022-12-01 18:30:00 +0000
148
- Flexitime.precision = :sec
149
- Flexitime.parse("2022-12-01T18:30:45.036711Z") # => 2022-12-01 18:30:45 +0000
150
- Flexitime.precision = :usec
151
- Flexitime.parse("2022-12-01T18:30:45.036711Z") # => 2022-12-01 18:30:45.036711 +0000
152
- ```
153
-
154
- ### Ambiguous Year Future Bias
155
-
156
- The Flexitime `ambiguous_year_future_bias` configuration option is used to determine the century when parsing a string containing a 2 digit year and defaults to 50.
157
-
158
- With a bias of 50, when the current year is 2020 the Time object's year is set from within a range of >= 1970 and <= 2069
159
-
160
- With a bias of 20, when the current year is 2020 the Time object's year is set from within a range of >= 2000 and <= 2099
161
-
162
- ```ruby
163
- Flexitime.parse("01/08/00").year # => 2000
164
- Flexitime.parse("01/08/69").year # => 2069
165
- Flexitime.parse("01/08/70").year # => 1970
166
- Flexitime.parse("01/08/99").year # => 1999
167
- ```
168
-
169
- ### Thread Safety
170
-
171
- The Flexitime `configuration` instance is stored in the currently executing thread as a thread-local variable in order to avoid conflicts with concurrent requests and make the variable threadsafe.
172
-
173
183
  ## Development
174
184
 
175
185
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -190,7 +200,7 @@ Bug reports and pull requests are welcome on [GitHub](https://github.com/CircleS
190
200
 
191
201
  ## Credits
192
202
 
193
- Tom Preston-Werner and [Chronic](https://github.com/mojombo/chronic) which was our previous go-to parser of choice but is unfortunately no longer maintained and performs natural language parsing that we do not require. It was a useful reference in particular for the `time_class` and `ambiguous_year_future_bias` configuration options.
203
+ Tom Preston-Werner and [Chronic](https://github.com/mojombo/chronic) which was our previous go-to parser of choice but is unfortunately no longer maintained and performs natural language parsing that we do not require. It was a useful reference in particular for the `ambiguous_year_future_bias` configuration options.
194
204
 
195
205
  Adam Meehan and [Timeliness](https://github.com/adzap/timeliness) which was a close match for our needs but proved to be too strict in its accepted formats particulary as we wanted to cater for a variety of date separators for both day/month/year and month/day/year date ordering. The gem provided some very useful inspiration in regards to code structure with forwarding for the configuration class and testing thread safety.
196
206
 
data/benchmark.rb CHANGED
@@ -25,14 +25,7 @@ Benchmark.bm(50) do |benchmark|
25
25
  end
26
26
  end
27
27
 
28
- benchmark.report("Flexitime.parse (with Time) datetime YMD") do
29
- n.times do
30
- Flexitime.parse("2021-08-12 12:30")
31
- end
32
- end
33
-
34
- benchmark.report("Flexitime.parse (with Time.zone) datetime YMD") do
35
- Flexitime.time_class = Time.zone
28
+ benchmark.report("Flexitime.parse datetime YMD") do
36
29
  n.times do
37
30
  Flexitime.parse("2021-08-12 12:30")
38
31
  end
@@ -52,14 +45,7 @@ Benchmark.bm(50) do |benchmark|
52
45
  end
53
46
  end
54
47
 
55
- benchmark.report("Flexitime.parse (with Time) datetime DMY HM") do
56
- n.times do
57
- Flexitime.parse("23/08/2021 12:30")
58
- end
59
- end
60
-
61
- benchmark.report("Flexitime.parse (with Time.zone) datetime DMY HM") do
62
- Flexitime.time_class = Time.zone
48
+ benchmark.report("Flexitime.parse datetime DMY HM") do
63
49
  n.times do
64
50
  Flexitime.parse("23/08/2021 12:30")
65
51
  end
@@ -79,14 +65,7 @@ Benchmark.bm(50) do |benchmark|
79
65
  end
80
66
  end
81
67
 
82
- benchmark.report("Flexitime.parse (with Time) datetime DMY HMS") do
83
- n.times do
84
- Flexitime.parse("23/08/2021 12:30:45")
85
- end
86
- end
87
-
88
- benchmark.report("Flexitime.parse (with Time.zone) datetime DMY HMS") do
89
- Flexitime.time_class = Time.zone
68
+ benchmark.report("Flexitime.parse datetime DMY HMS") do
90
69
  n.times do
91
70
  Flexitime.parse("23/08/2021 12:30:45")
92
71
  end
@@ -106,14 +85,7 @@ Benchmark.bm(50) do |benchmark|
106
85
  end
107
86
  end
108
87
 
109
- benchmark.report("Flexitime.parse (with Time) time HM") do
110
- n.times do
111
- Flexitime.parse("12:30")
112
- end
113
- end
114
-
115
- benchmark.report("Flexitime.parse (with Time.zone) time HM") do
116
- Flexitime.time_class = Time.zone
88
+ benchmark.report("Flexitime.parse time HM") do
117
89
  n.times do
118
90
  Flexitime.parse("12:30")
119
91
  end
@@ -133,14 +105,7 @@ Benchmark.bm(50) do |benchmark|
133
105
  end
134
106
  end
135
107
 
136
- benchmark.report("Flexitime.parse (with Time) time HMS") do
137
- n.times do
138
- Flexitime.parse("12:30:45")
139
- end
140
- end
141
-
142
- benchmark.report("Flexitime.parse (with Time.zone) time HMS") do
143
- Flexitime.time_class = Time.zone
108
+ benchmark.report("Flexitime.parse time HMS") do
144
109
  n.times do
145
110
  Flexitime.parse("12:30:45")
146
111
  end
data/flexitime.gemspec CHANGED
@@ -44,6 +44,7 @@ Gem::Specification.new do |spec|
44
44
  spec.add_development_dependency "rake", "~> 13.0"
45
45
  spec.add_development_dependency "rspec", "~> 3.0"
46
46
  spec.add_development_dependency "appraisal"
47
+ spec.add_development_dependency "simplecov"
47
48
 
48
49
  # For more information and examples about making a new gem, checkout our
49
50
  # guide at: https://bundler.io/guides/creating_gem.html
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "standard"
6
+ gem "activesupport", "~> 7.0"
7
+
8
+ gemspec path: "../"
@@ -7,16 +7,6 @@ module Flexitime
7
7
  class Configuration
8
8
  # = Configuration options
9
9
  #
10
- # == time_class
11
- # The time class used to create the Time object
12
- # defaulting to Time which will use the system local time zone.
13
- # This can be set to ActiveSupport::TimeZone to create a Time object
14
- # using the specified time zone
15
- #
16
- # Time.zone = "Europe/London"
17
- # Flexitime.time_class = Time.zone
18
- # Flexitime.parse("01/06/2021 17:00") # => Tue, 01 Jun 2021 17:00:00.000000000 BST +01:00
19
- #
20
10
  # == first_date_part
21
11
  # The first part of the date within the string, either :day or :month
22
12
  # This can be set manually otherwise when using the rails-I18n gem if the first element
@@ -55,12 +45,10 @@ module Flexitime
55
45
  # Flexitime.parse("01/08/70").year # => 1970
56
46
  # Flexitime.parse("01/08/99").year # => 1999
57
47
  #
58
- attr_accessor :time_class
59
48
  attr_reader :precision
60
49
  attr_accessor :ambiguous_year_future_bias
61
50
 
62
51
  def initialize
63
- @time_class = ::Time
64
52
  @first_date_part = nil
65
53
  @precision = :min
66
54
  @ambiguous_year_future_bias = 50
@@ -22,37 +22,56 @@ module Flexitime
22
22
  # that use a period with hours & minutes
23
23
  HOUR_MINUTE_REGEX = %r{^(\d{1,2})[:.](\d{2})\s?([aApP][mM])?\.?$}
24
24
 
25
- # Parse a String argument and create a Time object
26
- # using the configuration time class and desired precision.
25
+ # Parse a String argument and create an ActiveSupport::TimeWithZone object using Time.zone
26
+ #
27
+ # The parse uses either the argument or configuration first_date_part (:day or :month)
28
+ # and either the argument or configuration precision (:day, :hour, :min, :sec or :usec).
27
29
  #
28
30
  # When the String contains a date or date/time or time that matches the regular expressions
29
- # the time class local method is used to create a Time object
30
- # otherwise the time class parse method is used to create a Time object
31
+ # the Time.zone local method is used to create a TimeWithZone object
32
+ # otherwise the Time.zone parse method is used to create a TimeWithZone object
31
33
  # and for an invalid date, date/time or time nil is returned.
32
- def parse(str)
34
+ def parse(str, first_date_part: nil, precision: nil)
35
+ validate_options(first_date_part: first_date_part, precision: precision)
36
+
33
37
  str = str.is_a?(String) ? str : str.try(:to_str)
34
38
  return nil if str.blank?
35
39
 
36
- parts = extract_parts(str)
40
+ parts = extract_parts(str, first_date_part: first_date_part)
37
41
 
38
42
  if parts.present?
39
- create_time_from_parts(parts) if valid_date_parts?(parts)
43
+ create_time_from_parts(parts, precision: precision) if valid_date_parts?(parts)
40
44
  else
41
- time_class_parse(str)
45
+ time_zone_parse(str, precision: precision)
42
46
  end
43
47
  end
44
48
 
45
49
  private
46
50
 
51
+ # Returns an ActiveSupport::TimeZone
52
+ def time_zone
53
+ Time.zone ||= "UTC"
54
+ Time.zone
55
+ end
56
+
57
+ # Ensure the parse method options are valid
58
+ # reusing the configuration class validation which raises an exception
59
+ def validate_options(first_date_part: nil, precision: nil)
60
+ return if first_date_part.blank? && precision.blank?
61
+ config = Configuration.new
62
+ config.first_date_part = first_date_part if first_date_part.present?
63
+ config.precision = precision if precision.present?
64
+ end
65
+
47
66
  # Extract date and time parts and return a Hash containing the parts
48
67
  # or nil if either the date or time string does not match a regex
49
- def extract_parts(str)
68
+ def extract_parts(str, first_date_part: nil)
50
69
  date_str, time_str = separate_date_and_time(str)
51
70
 
52
- date_parts = extract_date_parts(date_str)
71
+ date_parts = extract_date_parts(date_str, first_date_part: first_date_part)
53
72
 
54
73
  if date_parts.blank?
55
- now = Flexitime.configuration.time_class.now
74
+ now = time_zone.now
56
75
  date_parts = {year: now.year, month: now.month, day: now.day}
57
76
  time_str = str.strip
58
77
  end
@@ -71,8 +90,8 @@ module Flexitime
71
90
  [parts.shift, parts.join(" ")]
72
91
  end
73
92
 
74
- def extract_date_parts(str)
75
- extract_iso_date_parts(str) || extract_local_date_parts(str)
93
+ def extract_date_parts(str, first_date_part: nil)
94
+ extract_iso_date_parts(str) || extract_local_date_parts(str, first_date_part: first_date_part)
76
95
  end
77
96
 
78
97
  def extract_iso_date_parts(str)
@@ -83,11 +102,12 @@ module Flexitime
83
102
  end
84
103
  end
85
104
 
86
- def extract_local_date_parts(str)
105
+ def extract_local_date_parts(str, first_date_part: nil)
87
106
  # match array returns ["23-08-1973", "23", "08", "1973"]
88
107
  parts = str.match(LOCAL_DATE_REGEX).to_a.pop(3).map(&:to_i)
89
108
  if parts.present?
90
- day, month = Flexitime.configuration.first_date_part == :day ? [parts.first, parts.second] : [parts.second, parts.first]
109
+ first_date_part ||= Flexitime.configuration.first_date_part
110
+ day, month = first_date_part == :day ? [parts.first, parts.second] : [parts.second, parts.first]
91
111
  {year: make_year(parts.third), month: month, day: day}
92
112
  end
93
113
  end
@@ -124,7 +144,7 @@ module Flexitime
124
144
  def make_year(year)
125
145
  return year if year.to_s.size > 2
126
146
 
127
- start_year = Flexitime.configuration.time_class.now.year - Flexitime.configuration.ambiguous_year_future_bias
147
+ start_year = time_zone.now.year - Flexitime.configuration.ambiguous_year_future_bias
128
148
  century = (start_year / 100) * 100
129
149
  full_year = century + year
130
150
  full_year < start_year ? full_year + 100 : full_year
@@ -135,38 +155,40 @@ module Flexitime
135
155
  meridiem.to_s.downcase == "pm" ? hour + 12 : hour
136
156
  end
137
157
 
138
- # Validate the day & month parts as Time#local accepts some invalid values
139
- # such as Time.local(2021,2,30) returning "2021-03-02"
158
+ # Validate the day & month parts as Time.zone#local accepts some invalid values
159
+ # such as Time.zone.local(2021,2,30) returning "2021-03-02"
140
160
  def valid_date_parts?(parts)
141
161
  parts[:month] >= 1 && parts[:month] <= 12 && parts[:day] >= 1 &&
142
162
  parts[:day] <= Time.days_in_month(parts[:month], parts[:year])
143
163
  end
144
164
 
145
- # Create a Time object using only those parts required for the configuration precision
146
- def create_time_from_parts(parts)
147
- time = Flexitime.configuration.time_class.local(*local_args_for_precision(parts))
165
+ # Create a TimeWithZone object object using only those parts required for the configuration precision
166
+ def create_time_from_parts(parts, precision: nil)
167
+ time = time_zone.local(*local_args_for_precision(parts, precision: precision))
148
168
  parts[:utc] ? (time + time.utc_offset) : time
149
169
  rescue
150
170
  end
151
171
 
152
172
  # Returns the date/time parts required for the configuration precision
153
- def local_args_for_precision(parts)
173
+ def local_args_for_precision(parts, precision: nil)
154
174
  keys = [:year, :month, :day, :hour, :min, :sec, :usec]
155
- index = keys.index(Flexitime.configuration.precision)
175
+ precision ||= Flexitime.configuration.precision
176
+ index = keys.index(precision)
156
177
  keys[0..index].map { |key| parts[key] }
157
178
  end
158
179
 
159
- # Parse the string using the time class and set the configuration precision
160
- def time_class_parse(str)
161
- time = Flexitime.configuration.time_class.parse(str)
162
- set_precision(time)
180
+ # Parse the string using Time.zone and set the configuration precision
181
+ def time_zone_parse(str, precision: nil)
182
+ time = time_zone.parse(str)
183
+ set_precision(time, precision: precision)
163
184
  rescue
164
185
  end
165
186
 
166
187
  # Set the precision, first checking if this is necessary
167
188
  # to avoid the overhead of calling the change method
168
- def set_precision(time)
169
- index = PRECISIONS.index(Flexitime.configuration.precision)
189
+ def set_precision(time, precision: nil)
190
+ precision ||= Flexitime.configuration.precision
191
+ index = PRECISIONS.index(precision)
170
192
  dismiss_part = PRECISIONS[index + 1]
171
193
  excess = PRECISIONS[(index + 1)..-1].sum { |key| time.send(key) }
172
194
  excess > 0 ? time.change(dismiss_part => 0) : time
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Flexitime
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/flexitime.rb CHANGED
@@ -5,6 +5,13 @@ require "forwardable"
5
5
 
6
6
  # Active Support Core Extensions
7
7
  # https://guides.rubyonrails.org/active_support_core_extensions.html
8
+ begin
9
+ # workaround for activesupport 7.0
10
+ # ref: https://github.com/rails/rails/issues/43851
11
+ require "active_support/isolated_execution_state"
12
+ rescue LoadError
13
+ end
14
+ require "active_support/time" # Time.zone
8
15
  require "active_support/core_ext/object/blank" # blank? and present?
9
16
  require "active_support/core_ext/array/access" # array second/third/fourth
10
17
  require "active_support/core_ext/time/calculations" # Time.days_in_month
@@ -16,8 +23,8 @@ require_relative "flexitime/parser"
16
23
  module Flexitime
17
24
  class << self
18
25
  extend Forwardable
19
- def_delegators :configuration, :time_class, :first_date_part, :precision, :ambiguous_year_future_bias
20
- def_delegators :configuration, :time_class=, :first_date_part=, :precision=, :ambiguous_year_future_bias=
26
+ def_delegators :configuration, :first_date_part, :precision, :ambiguous_year_future_bias
27
+ def_delegators :configuration, :first_date_part=, :precision=, :ambiguous_year_future_bias=
21
28
 
22
29
  include Flexitime::Parser
23
30
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flexitime
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hilton
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-22 00:00:00.000000000 Z
11
+ date: 2022-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: Ruby date/time string parser for common formats and different date orders
70
84
  email:
71
85
  - 449774+chrismhilton@users.noreply.github.com
@@ -98,6 +112,7 @@ files:
98
112
  - gemfiles/activesupport_5_2.gemfile
99
113
  - gemfiles/activesupport_6_0.gemfile
100
114
  - gemfiles/activesupport_6_1.gemfile
115
+ - gemfiles/activesupport_7_0.gemfile
101
116
  - lib/flexitime.rb
102
117
  - lib/flexitime/configuration.rb
103
118
  - lib/flexitime/parser.rb