clir-data_manager 0.3.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.
@@ -0,0 +1,366 @@
1
+ =begin
2
+
3
+ Class Periode
4
+ -------------
5
+ Pour gérer une période de temps, à partir d'une date (:from_date)
6
+ jusqu'à une autre date (:to_date)
7
+
8
+ Initialisation possible avec :
9
+ - des vraies dates (Date)
10
+ - des vrais temps (Time)
11
+ - des dates stings en "JJ/MM/AAAA" (ATTENTION ! Format FR)
12
+ - des dates strings inverses "AAAA/MM/JJ"
13
+ - des dates strings raccourcis, par exemple seulement l'année
14
+ ou le mois
15
+ - des timestamps/nombre de secondes
16
+
17
+ On obtient ensuite des méthodes pratiques et particulièrement la
18
+ méthode :human_period qui détecte si la date définit une année,
19
+ un trimestre, un mois, etc.
20
+
21
+ <Periode>#human_period ou #as_human
22
+ => Période au format humain
23
+ ("année xxxx", "1er trimestre 2014", "mois d’avril 2009",
24
+ "semaine 25 de l’année 2012",
25
+ "du 1er avril au 1er juin 2022")
26
+
27
+ <Periode>#du_au
28
+ => "du <date départ> au <date fin>"
29
+
30
+ <Periode>#from
31
+ => date de départ ({Time})
32
+
33
+ <Periode>#to
34
+ => date {Time} de fin
35
+
36
+ <Periode>#time_in?(time)
37
+ => true si le temps +time+ est dans la période
38
+
39
+
40
+ =end
41
+ require 'date'
42
+ # require_relative 'Date_utils'
43
+
44
+ class Periode
45
+
46
+ # Pour définir une période (une date de départ et de fin)
47
+ def self.choose(params = nil)
48
+ params ||= {}
49
+ params.merge!(from_date: params.delete(:default)) if params.key?(:default)
50
+ params.key?(:from_date) || params.merge!(from_date: nil)
51
+ params.key?(:to_date) || params.merge!(to_date: nil)
52
+ params[:from_date] = params[:from_date].to_s if params[:from_date].is_a?(Integer)
53
+ while true
54
+ date_default = params[:from_date] || Time.now
55
+ if not date_default.is_a?(String)
56
+ date_default = date_default.strftime('%d/%m/%Y')
57
+ end
58
+ params[:from_date] = ask_for_a_date("De la date", date_default)
59
+ if params[:from_date].is_a?(String)
60
+ if params[:from_date].match?(/^[0-9]{4}$/)
61
+ params.merge!({
62
+ from_date: "01/01/#{params[:from_date]}",
63
+ to_date: "01/01/#{params[:from_date].to_i + 1}"
64
+ })
65
+ break
66
+ elsif params[:from_date].match?(/^[0-9]{1,2}\/[0-9]{4}$/)
67
+ #
68
+ # Seulement le mois donné
69
+ #
70
+ mois, annee = params[:from_date].split('/').collect{|n|n.to_i}
71
+ fromdate, todate =
72
+ if mois == 12
73
+ ["01/12/#{annee}", "01/01/#{annee + 1}"]
74
+ else
75
+ ["01/#{mois.to_s.rjust(2,'0')}/#{annee}", "01/#{(mois + 1).to_s.rjust(2,'0')}/#{annee}"]
76
+ end
77
+ params.merge!(from_date:fromdate, to_date:todate)
78
+ break
79
+ end
80
+ else
81
+ params[:to_date] = ask_for_a_date("À la date", date_default)
82
+ # --- Vérifications ---
83
+ if params[:from_date] > params[:to_date]
84
+ erreur "La date de début ne peut être supérieure à la date de fin…"
85
+ else
86
+ break
87
+ end
88
+ end
89
+ end #/boucle
90
+
91
+ return new(params[:from_date], params[:to_date], params[:options])
92
+ end
93
+
94
+
95
+ attr_reader :from_date, :to_date
96
+
97
+ def initialize(from_date, to_date = nil, options = nil)
98
+ from_date, to_date = defaultize(from_date, to_date)
99
+ @from_date = date_from(from_date)
100
+ @to_date = date_from(to_date)
101
+ end
102
+
103
+ def inspect
104
+ @inspect ||= "#<Periode #{as_la_periode}>"
105
+ end
106
+
107
+ # @public
108
+ #
109
+ # @return [Boolean] Si le temps +time+ (qui peut être exprimé par
110
+ # une multitude de choses) se trouve dans la période courante
111
+ # @param [Any] time Le temps à tester, qui doit pouvoir être évalué par 'date_from'
112
+ # [String] "JJ/MM/AAAA"
113
+ # [Integer]
114
+ def time_in?(time)
115
+ return false if time.nil?
116
+ time = date_from(time)
117
+ return time >= from_date && time <= to_date
118
+ end
119
+
120
+ ##
121
+ # Méthode qui reçoit les deux premiers arguments de l'initiation
122
+ # pour les interpréter au besoin. Par exemple, on peut fournir
123
+ # seulement une année (dans +from_date+) ou un mois (MM/AAAA)
124
+ #
125
+ # [N0001]
126
+ # Une année chiffrée (2022) peut avoir été donnée
127
+ #
128
+ REG_ANNEE = /^[0-9]{4}$/
129
+ REG_MOIS = /^[0-9]{1,2}\/[0-9]{4}$/
130
+ def defaultize(from_date, to_date)
131
+ return [from_date, to_date] if to_date.is_a?(Time) || to_date.is_a?(Date)
132
+ from_date = from_date.to_s # cf. [N0001]
133
+ if to_date.nil?
134
+ if from_date.match?(REG_ANNEE)
135
+ #
136
+ # Période d'une année
137
+ #
138
+ annee = from_date.to_i
139
+ from_date = "01/01/#{annee}"
140
+ to_date = "01/01/#{annee + 1}"
141
+ elsif from_date.match?(REG_MOIS)
142
+ #
143
+ # Période d'un mois
144
+ #
145
+ mois, annee = from_date.split('/')
146
+ from_date = "01/#{mois.rjust(2,'0')}/#{annee}"
147
+ if mois.to_i == 12
148
+ mois, annee = [0, annee.to_i + 1]
149
+ end
150
+ to_date = "01/#{(mois.to_i+1).to_s.rjust(2,'0')}/#{annee}"
151
+ end
152
+ end
153
+ return [from_date, to_date]
154
+ end
155
+
156
+ # - raccourcis -
157
+ def from; from_date end
158
+ def to; to_date end
159
+
160
+ # - clé pour table -
161
+ def as_hash_key
162
+ @as_hash_key ||= "#{from.to_i}-#{to.to_i}"
163
+ end
164
+
165
+ ##
166
+ # @return la durée en secondes de la période
167
+ def duration
168
+ @duration ||= to_timestamp - from_timestamp
169
+ end
170
+
171
+ def from_timestamp
172
+ @from_timestamp ||= from_date.to_i
173
+ end
174
+ def to_timestamp
175
+ @to_timestamp ||= to_date.to_i
176
+ end
177
+
178
+ ##
179
+ # @return "l'année ..." ou "Le trimestre", etc.
180
+ #
181
+ def as_la_periode
182
+ @as_la_periode ||= begin
183
+ if debut_et_fin_de_semaine?
184
+ "la #{as_human}"
185
+ elsif premier_jour_mois?
186
+ if debut_et_fin_de_mois?
187
+ "le mois d#{human_month.match?(/^[ao]/) ? '’' : 'e '}#{human_month} #{human_year}"
188
+ elsif debut_et_fin_trimestre?
189
+ "#{hindex_trimestre} trimestre #{human_year}"
190
+ elsif debut_et_fin_annee?
191
+ "l’année #{human_year}"
192
+ else
193
+ "le #{human_period_jours}"
194
+ end
195
+ else
196
+ "le #{human_period_jours}"
197
+ end
198
+ end
199
+ end
200
+ alias :to_s :as_la_periode
201
+
202
+ ##
203
+ # @return un texte comme "de l'année 2021"
204
+ def as_du_au
205
+ @as_du_au ||= begin
206
+ if debut_et_fin_de_semaine?
207
+ "de la #{as_human}"
208
+ elsif premier_jour_mois?
209
+ if debut_et_fin_de_mois?
210
+ "du mois d#{human_month.match?(/^[ao]/) ? '’' : 'e '}#{human_month} #{human_year}"
211
+ elsif debut_et_fin_trimestre?
212
+ "#{hindex_trimestre} trimestre #{human_year}"
213
+ elsif debut_et_fin_annee?
214
+ "de l’année #{human_year}"
215
+ else
216
+ "du #{human_period_jours}"
217
+ end
218
+ else
219
+ "du #{human_period_jours}"
220
+ end
221
+ end
222
+ end
223
+
224
+ def human_period
225
+ @human_period ||= begin
226
+ # Est-ce une semaine ?
227
+ if debut_et_fin_de_semaine?
228
+ "semaine #{from_date.to_date.cweek} de l’année #{human_year}"
229
+ elsif premier_jour_mois?
230
+ if debut_et_fin_de_mois?
231
+ "mois d#{human_month.match?(/^[ao]/) ? '’' : 'e '}#{human_month} #{human_year}"
232
+ elsif debut_et_fin_trimestre?
233
+ "#{hindex_trimestre} trimestre #{human_year}"
234
+ elsif debut_et_fin_annee?
235
+ "Année #{human_year}"
236
+ else
237
+ human_period_jours
238
+ end
239
+ else
240
+ human_period_jours
241
+ end
242
+ end
243
+ end
244
+ alias :as_human :human_period
245
+
246
+ ##
247
+ # @return la période simple en jour (lorsqu'aucun période humaine
248
+ # n'a été trouvée)
249
+ #
250
+ def human_period_jours
251
+ @human_period_jours ||= begin
252
+ formated_from = formate_date(from_date, **{no_time: true, update_format:true, verbal: true})
253
+ if from_date.year == to_date.year
254
+ # Les années sont identiques, on les rassemble à la fin
255
+ formated_from = formated_from[0..-6]
256
+ end
257
+ "#{formated_from} au #{formate_date(to_date, **{no_time: true, update_format:true, verbal: true})}"
258
+ end
259
+ end
260
+
261
+ def as_human_jours
262
+ @as_human_jours ||= "du #{human_period_jours}"
263
+ end
264
+ alias :du_au :as_human_jours
265
+
266
+ ##
267
+ # @return TRUE si la période correspond à un début et une fin de
268
+ # semaine
269
+ def debut_et_fin_de_semaine?
270
+ :TRUE == @is_debut_et_fin_de_semaine ||= begin
271
+ from_date.wday == 1 && duration.proche?(7.days) ? :TRUE : :FALSE
272
+ end
273
+ end
274
+
275
+ ##
276
+ # @return TRUE si la période commence le premier jour d'un mois
277
+ def premier_jour_mois?
278
+ from_date.day == 1
279
+ end
280
+
281
+ ##
282
+ # @return TRUE si la période correspond à un mois
283
+ #
284
+ def debut_et_fin_de_mois?
285
+ from_date.day == 1 || return
286
+ :TRUE == @is_debut_et_fin_de_mois ||= begin
287
+ d = (from_date.to_date >> 1).to_time
288
+ to_date.proche?(d) ? :TRUE : :FALSE
289
+ end
290
+ end
291
+
292
+ ##
293
+ # @return TRUE si la période correspond à un trimestre
294
+ #
295
+ def debut_et_fin_trimestre?
296
+ from_date.day == 1 || return
297
+ :TRUE == @is_debut_et_fin_trimestre ||= begin
298
+ d = from_date.to_date >> 3
299
+ d = Date.new(d.year, d.month, 1).to_time
300
+ to_date.proche?(d) ? :TRUE : :FALSE
301
+ end
302
+ end
303
+
304
+ ##
305
+ # @return TRUE is la période correspond à une année
306
+ #
307
+ def debut_et_fin_annee?
308
+ from_date.day == 1 || return
309
+ from_date.month == 1 || return
310
+ :TRUE == @is_debut_et_fin_annee ||= begin
311
+ dcomp = Time.new(from_date.year + 1, 1, 1)
312
+ to_date.proche?(dcomp) ? :TRUE : :FALSE
313
+ end
314
+ end
315
+
316
+ # Mois humain (en prenant le mois de from_date)
317
+ def human_month
318
+ @human_month ||= MOIS[from_date.month][:long]
319
+ end
320
+ # Année (en prenant l'année de from_date)
321
+ def human_year
322
+ @human_year ||= from_date.year
323
+ end
324
+
325
+ ##
326
+ # Index du trimestre (de from_date)
327
+ def index_trimestre
328
+ @index_trimestre ||= (from_date.month.to_f / 3).ceil
329
+ end
330
+
331
+ def hindex_trimestre
332
+ @hindex_trimestre ||= begin
333
+ "#{index_trimestre}e#{index_trimestre > 1 ? '' : 'r'}"
334
+ end
335
+ end
336
+
337
+ # dernier jour du mois (en prenant le mois de to_date)
338
+ def last_day_of_the_month
339
+ @last_day_of_the_month ||= Date.civil(to_date.year, to_date.month, -1).day
340
+ end
341
+ def from_mois
342
+ @from_mois ||= from_date.month
343
+ end
344
+ def to_mois
345
+ @to_mois ||= to_mois
346
+ end
347
+ end
348
+
349
+ class Time
350
+ def proche?(t)
351
+ self.to_i.proche?(t.to_i)
352
+ end
353
+ end
354
+ class Integer
355
+ # Retourne true si le nombre (de secondes correspondant à une
356
+ # durée) est ± une demi-heure
357
+ def proche?(laps)
358
+ self.between?(laps - 1800, laps + 1800)
359
+ end
360
+ def days
361
+ self * 24 * 3600
362
+ end
363
+ alias :day :days
364
+ end
365
+
366
+
@@ -0,0 +1,98 @@
1
+ =begin
2
+
3
+ Class Clir::PrecedencedList
4
+ =end
5
+ module Clir
6
+ class PrecedencedList < Array
7
+
8
+ ##
9
+ # Instantiation
10
+ #
11
+ # @param liste {Array} La liste des items
12
+ # @param list_name {String} Un nom unique pour cette liste de
13
+ # valeur. Si ce n'est pas un nom unique, des comportements
14
+ # inattendus se produiront.
15
+ #
16
+ def initialize(liste, list_name)
17
+ super(liste)
18
+ liste.first.is_a?(Hash) || raise("Pour pouvoir gérer les précédences d'une liste, il faut que les éléments soit des dictionnaires (Hash).")
19
+ liste.first.key?(:value) || raise("Pour pouvoir gérer les précédences d'une liste, il faut que les éléments définissent l'attribut unique :value.")
20
+ @list_name = list_name
21
+ end
22
+
23
+ # = main =
24
+ #
25
+ # Méthode principale qui retourne les items gérés au niveau de
26
+ # la précédence.
27
+ #
28
+ def to_prec
29
+ if precedence_exist?
30
+ @table = nil # Pour forcer
31
+ liste = precedences.map do |value|
32
+ table.delete(value)
33
+ end.compact # les items supprimés
34
+ #
35
+ # On ajoute les items restants
36
+ #
37
+ table.each do |value, ditem|
38
+ liste << ditem
39
+ end
40
+ return liste
41
+ else
42
+ return self
43
+ end
44
+ end
45
+
46
+ # @public
47
+ # = main =
48
+ #
49
+ # Méthode principale pour enregistrer le dernier item choisi
50
+ #
51
+ def set_last(value)
52
+ precedences.delete(value)
53
+ @precedences.unshift(value)
54
+ save
55
+ end
56
+
57
+
58
+ def table
59
+ @table ||= begin
60
+ tbl = {}
61
+ self.each { |ditem| tbl.merge!(ditem[:value] => ditem) }
62
+ tbl
63
+ end
64
+ end
65
+
66
+ def save
67
+ File.write(path, precedences.join("\n"))
68
+ end
69
+
70
+ def precedences
71
+ @precedences ||= begin
72
+ if precedence_exist?
73
+ # puts "Je lis le fichier de précédence : #{path}".orange
74
+ # sleep 4
75
+ File.read(path).split("\n")
76
+ else [] end
77
+ end
78
+ end
79
+
80
+ def precedence_exist?
81
+ File.exist?(path)
82
+ end
83
+
84
+ def path
85
+ @path ||= File.join(self.class.folder, "#{@list_name}.precedences")
86
+ end
87
+
88
+ # @note
89
+ # En mode test, il faut le refaire chaque fois
90
+ def self.folder
91
+ if test?
92
+ mkdir(File.join(Dir.home, 'TESTS', '.precedences'))
93
+ else
94
+ @@folder ||= mkdir(File.join(Dir.home, '.precedences'))
95
+ end
96
+ end
97
+ end #/class PrecedencedList
98
+ end #/module Clir