Module: Vishnu::StandardTime
- Defined in:
- lib/vishnu/standard_time.rb,
lib/vishnu/standard_time/cli.rb,
lib/vishnu/standard_time/railtie.rb,
lib/vishnu/standard_time/rails_helpers.rb
Defined Under Namespace
Modules: RailsHelpers Classes: CLI, Duration, FocusSession, Parts, Plan, Railtie
Constant Summary collapse
- VERSION =
"0.2.0"- VST_SPEC_VERSION =
VERSION- STANDARD_NAME =
"Vishnu Standard Time"- WEBSITE_URL =
"https://time.vishnu.store"- VST_EPOCH_UNIX_MS =
1_767_225_600_000- MS_PER_DAY =
86_400_000- FLOWS_PER_SECOND =
3- FLOWS_PER_MINUTE =
180- FLOWS_PER_HOUR =
10_800- FLOWS_PER_DAY =
259_200- FLOW_LENGTH_MS_TEXT =
"333⅓"- VICKS_PER_DAY =
Legacy/compat display units kept for interop with early VST builds.
100_000- MS_PER_VICK =
864
Class Method Summary collapse
- .after(flows: nil, seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil, from: local_now) ⇒ Object
- .comma6(value) ⇒ Object
- .duration(flows: nil, seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil) ⇒ Object
- .duration_ms_for(flows: nil, seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil) ⇒ Object
- .flows_elapsed_today(parts = local_now) ⇒ Object
- .flows_for(seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil) ⇒ Object
- .flows_left_today(parts = local_now) ⇒ Object
- .focus(flows: nil, seconds: nil, minutes: nil, hours: nil, label: nil, started_at: local_now) ⇒ Object
- .format_flow_count(parts) ⇒ Object
- .format_flow_of_minute(parts) ⇒ Object
- .format_flows_left(parts = local_now) ⇒ Object
- .format_number(value, pad: false) ⇒ Object
- .format_percent_of_day(parts, digits = 2) ⇒ Object
- .format_percent_remaining(parts, digits = 2) ⇒ Object
- .format_prompt(parts = local_now) ⇒ Object
- .format_vst(parts) ⇒ Object
- .format_vst_day(parts) ⇒ Object
- .format_vst_precise(parts) ⇒ Object
- .from_day_ms(day, day_ms) ⇒ Object
- .from_unix_ms(unix_ms) ⇒ Object
- .hours_for(flows:) ⇒ Object
- .local_now(time = Time.now) ⇒ Object
- .minutes_for(flows:) ⇒ Object
- .now ⇒ Object
- .parse_duration(input) ⇒ Object
- .parts_from_day_ms(day, day_ms, unix_ms: nil, source: "standard") ⇒ Object
- .percent_remaining(parts = local_now) ⇒ Object
- .plan(&block) ⇒ Object
- .seconds_for(flows:) ⇒ Object
- .stamp(message = nil, parts = local_now) ⇒ Object
- .standard_link ⇒ Object
- .svg_badge(parts = local_now, width: 560, height: 32) ⇒ Object
- .to_h(parts) ⇒ Object
- .to_unix_ms(parts) ⇒ Object
- .unix_ms_for(parts) ⇒ Object
- .until_flow(target_flow, from: local_now) ⇒ Object
- .website_url ⇒ Object
Class Method Details
.after(flows: nil, seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil, from: local_now) ⇒ Object
390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/vishnu/standard_time.rb', line 390 def after(flows: nil, seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil, from: local_now) ms = unix_ms_for(from) + duration_ms_for( flows: flows, seconds: seconds, minutes: minutes, hours: hours, days: days, milliseconds: milliseconds ) from.respond_to?(:source) && from.source == "local" ? local_now(Time.at(ms / 1000.0)) : from_unix_ms(ms) end |
.comma6(value) ⇒ Object
270 271 272 |
# File 'lib/vishnu/standard_time.rb', line 270 def comma6(value) format_number(value, pad: true) end |
.duration(flows: nil, seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil) ⇒ Object
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/vishnu/standard_time.rb', line 351 def duration(flows: nil, seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil) ms = duration_ms_for( flows: flows, seconds: seconds, minutes: minutes, hours: hours, days: days, milliseconds: milliseconds ) Duration.new( flows: flows || flows_for(milliseconds: ms), milliseconds: ms, seconds: ms / 1000.0 ) end |
.duration_ms_for(flows: nil, seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil) ⇒ Object
340 341 342 343 344 345 346 347 348 349 |
# File 'lib/vishnu/standard_time.rb', line 340 def duration_ms_for(flows: nil, seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil) ms = 0.0 ms += flows.to_f * 1000.0 / FLOWS_PER_SECOND if flows ms += seconds.to_f * 1000 if seconds ms += minutes.to_f * 60_000 if minutes ms += hours.to_f * 3_600_000 if hours ms += days.to_f * MS_PER_DAY if days ms += milliseconds.to_f if milliseconds ms.positive? ? ms.ceil : ms.floor end |
.flows_elapsed_today(parts = local_now) ⇒ Object
306 307 308 |
# File 'lib/vishnu/standard_time.rb', line 306 def flows_elapsed_today(parts = local_now) Integer(parts.flow) end |
.flows_for(seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil) ⇒ Object
318 319 320 321 322 323 324 325 326 |
# File 'lib/vishnu/standard_time.rb', line 318 def flows_for(seconds: nil, minutes: nil, hours: nil, days: nil, milliseconds: nil) total_seconds = 0.0 total_seconds += seconds.to_f if seconds total_seconds += minutes.to_f * 60 if minutes total_seconds += hours.to_f * 3600 if hours total_seconds += days.to_f * 86_400 if days total_seconds += milliseconds.to_f / 1000 if milliseconds (total_seconds * FLOWS_PER_SECOND).round end |
.flows_left_today(parts = local_now) ⇒ Object
310 311 312 |
# File 'lib/vishnu/standard_time.rb', line 310 def flows_left_today(parts = local_now) [FLOWS_PER_DAY - flows_elapsed_today(parts), 0].max end |
.focus(flows: nil, seconds: nil, minutes: nil, hours: nil, label: nil, started_at: local_now) ⇒ Object
418 419 420 421 |
# File 'lib/vishnu/standard_time.rb', line 418 def focus(flows: nil, seconds: nil, minutes: nil, hours: nil, label: nil, started_at: local_now) total = flows || flows_for(seconds: seconds, minutes: minutes, hours: hours) FocusSession.new(total_flows: total, label: label, started_at: started_at) end |
.format_flow_count(parts) ⇒ Object
274 275 276 |
# File 'lib/vishnu/standard_time.rb', line 274 def format_flow_count(parts) "#{comma6(parts.flow)} / #{format_number(FLOWS_PER_DAY)} flows" end |
.format_flow_of_minute(parts) ⇒ Object
278 279 280 |
# File 'lib/vishnu/standard_time.rb', line 278 def format_flow_of_minute(parts) "FLOW #{parts.flow_of_minute.to_s.rjust(3, "0")} / 179" end |
.format_flows_left(parts = local_now) ⇒ Object
290 291 292 |
# File 'lib/vishnu/standard_time.rb', line 290 def format_flows_left(parts = local_now) "#{format_number(flows_left_today(parts))} flows left today" end |
.format_number(value, pad: false) ⇒ Object
263 264 265 266 267 268 |
# File 'lib/vishnu/standard_time.rb', line 263 def format_number(value, pad: false) s = Integer(value).abs.to_s s = s.rjust(6, "0") if pad grouped = s.reverse.gsub(/(\d{3})(?=\d)/, "\\1,").reverse Integer(value).negative? ? "-#{grouped}" : grouped end |
.format_percent_of_day(parts, digits = 2) ⇒ Object
282 283 284 |
# File 'lib/vishnu/standard_time.rb', line 282 def format_percent_of_day(parts, digits = 2) "%0#{digits + 3}.#{digits}f%% OF DAY" % parts.percent_of_day end |
.format_percent_remaining(parts, digits = 2) ⇒ Object
286 287 288 |
# File 'lib/vishnu/standard_time.rb', line 286 def format_percent_remaining(parts, digits = 2) "%0#{digits + 3}.#{digits}f%% DAY REMAINING" % percent_remaining(parts) end |
.format_prompt(parts = local_now) ⇒ Object
302 303 304 |
# File 'lib/vishnu/standard_time.rb', line 302 def format_prompt(parts = local_now) "VST #{comma6(parts.flow)}/#{format_number(FLOWS_PER_DAY)} #{format('%.2f%%', parts.percent_of_day)}" end |
.format_vst(parts) ⇒ Object
294 295 296 |
# File 'lib/vishnu/standard_time.rb', line 294 def format_vst(parts) "VST #{format_vst_day(parts)} · #{format_flow_count(parts)} · #{format_percent_of_day(parts)}" end |
.format_vst_day(parts) ⇒ Object
258 259 260 261 |
# File 'lib/vishnu/standard_time.rb', line 258 def format_vst_day(parts) sign = parts.day.negative? ? "-" : "+" "D#{sign}#{parts.day.abs.to_s.rjust(6, "0")}" end |
.format_vst_precise(parts) ⇒ Object
298 299 300 |
# File 'lib/vishnu/standard_time.rb', line 298 def format_vst_precise(parts) "VST #{format_vst_day(parts)} · #{format_flow_of_minute(parts)} · #{format_flow_count(parts)} · #{format_percent_of_day(parts)}" end |
.from_day_ms(day, day_ms) ⇒ Object
212 213 214 |
# File 'lib/vishnu/standard_time.rb', line 212 def from_day_ms(day, day_ms) parts_from_day_ms(Integer(day), Integer(day_ms)) end |
.from_unix_ms(unix_ms) ⇒ Object
206 207 208 209 210 |
# File 'lib/vishnu/standard_time.rb', line 206 def from_unix_ms(unix_ms) ms = Integer(unix_ms) day, day_ms = (ms - VST_EPOCH_UNIX_MS).divmod(MS_PER_DAY) parts_from_day_ms(day, day_ms, unix_ms: ms, source: "standard") end |
.hours_for(flows:) ⇒ Object
336 337 338 |
# File 'lib/vishnu/standard_time.rb', line 336 def hours_for(flows:) seconds_for(flows: flows) / 3600.0 end |
.local_now(time = Time.now) ⇒ Object
239 240 241 242 243 244 245 |
# File 'lib/vishnu/standard_time.rb', line 239 def local_now(time = Time.now) day_ms = ((time.hour * 60 + time.min) * 60 + time.sec) * 1000 + (time.usec / 1000) local_midnight_as_utc = Time.utc(time.year, time.month, time.day) epoch = Time.at(VST_EPOCH_UNIX_MS / 1000, in: "+00:00") day = ((local_midnight_as_utc - epoch) / 86_400).floor parts_from_day_ms(day, day_ms, unix_ms: (time.to_f * 1000).floor, source: "local") end |
.minutes_for(flows:) ⇒ Object
332 333 334 |
# File 'lib/vishnu/standard_time.rb', line 332 def minutes_for(flows:) seconds_for(flows: flows) / 60.0 end |
.now ⇒ Object
235 236 237 |
# File 'lib/vishnu/standard_time.rb', line 235 def now from_unix_ms((Time.now.to_f * 1000).floor) end |
.parse_duration(input) ⇒ Object
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/vishnu/standard_time.rb', line 367 def parse_duration(input) text = input.to_s.strip.downcase raise ArgumentError, "duration is required" if text.empty? case text when /\A([+-]?\d+(?:\.\d+)?)\s*(f|flow|flows)?\z/ flows = Regexp.last_match(1).to_f duration(flows: flows) when /\A([+-]?\d+(?:\.\d+)?)\s*(ms|millisecond|milliseconds)\z/ duration(milliseconds: Regexp.last_match(1).to_f) when /\A([+-]?\d+(?:\.\d+)?)\s*(s|sec|secs|second|seconds)\z/ duration(seconds: Regexp.last_match(1).to_f) when /\A([+-]?\d+(?:\.\d+)?)\s*(m|min|mins|minute|minutes)\z/ duration(minutes: Regexp.last_match(1).to_f) when /\A([+-]?\d+(?:\.\d+)?)\s*(h|hr|hrs|hour|hours)\z/ duration(hours: Regexp.last_match(1).to_f) when /\A([+-]?\d+(?:\.\d+)?)\s*(d|day|days)\z/ duration(days: Regexp.last_match(1).to_f) else raise ArgumentError, "unknown duration '#{input}'. Try 4500, 4500f, 25m, 90s, 1h." end end |
.parts_from_day_ms(day, day_ms, unix_ms: nil, source: "standard") ⇒ Object
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/vishnu/standard_time.rb', line 216 def parts_from_day_ms(day, day_ms, unix_ms: nil, source: "standard") day_ms = Integer(day_ms) raise ArgumentError, "day_ms must be from 0 through 86399999" if day_ms.negative? || day_ms >= MS_PER_DAY flow = (day_ms * FLOWS_PER_DAY) / MS_PER_DAY Parts.new( day: Integer(day), day_ms: day_ms, flow: flow, flow_of_minute: flow % FLOWS_PER_MINUTE, second_of_day: day_ms / 1000, percent_of_day: (day_ms.to_f / MS_PER_DAY) * 100, vick: day_ms / MS_PER_VICK, pulse: day_ms % MS_PER_VICK, unix_ms: unix_ms, source: source ) end |
.percent_remaining(parts = local_now) ⇒ Object
314 315 316 |
# File 'lib/vishnu/standard_time.rb', line 314 def percent_remaining(parts = local_now) [100.0 - parts.percent_of_day.to_f, 0.0].max end |
.plan(&block) ⇒ Object
423 424 425 426 427 |
# File 'lib/vishnu/standard_time.rb', line 423 def plan(&block) p = Plan.new p.instance_eval(&block) if block p end |
.seconds_for(flows:) ⇒ Object
328 329 330 |
# File 'lib/vishnu/standard_time.rb', line 328 def seconds_for(flows:) flows.to_f / FLOWS_PER_SECOND end |
.stamp(message = nil, parts = local_now) ⇒ Object
429 430 431 432 |
# File 'lib/vishnu/standard_time.rb', line 429 def stamp( = nil, parts = local_now) suffix = .to_s.strip suffix.empty? ? "[#{format_vst(parts)}]" : "[#{format_vst(parts)}] #{suffix}" end |
.standard_link ⇒ Object
202 203 204 |
# File 'lib/vishnu/standard_time.rb', line 202 def standard_link "#{STANDARD_NAME} — #{WEBSITE_URL}" end |
.svg_badge(parts = local_now, width: 560, height: 32) ⇒ Object
434 435 436 437 438 439 440 441 442 443 444 445 446 |
# File 'lib/vishnu/standard_time.rb', line 434 def svg_badge(parts = local_now, width: 560, height: 32) text = CGI.escapeHTML(format_vst(parts)) url = CGI.escapeHTML(WEBSITE_URL) <<~SVG.strip <svg xmlns="http://www.w3.org/2000/svg" width="#{Integer(width)}" height="#{Integer(height)}" role="img" aria-label="#{text}"> <title>#{text}</title> <a href="#{url}" target="_blank"> <rect width="100%" height="100%" rx="6"/> <text x="12" y="21" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="13" fill="white">#{text}</text> </a> </svg> SVG end |
.to_h(parts) ⇒ Object
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 |
# File 'lib/vishnu/standard_time.rb', line 448 def to_h(parts) { scale: "VST", version: VST_SPEC_VERSION, day: parts.day.to_s, flow: parts.flow, flow_of_minute: parts.flow_of_minute, flows_per_day: FLOWS_PER_DAY, flows_left_today: flows_left_today(parts), percent_of_day: parts.percent_of_day.round(6), percent_remaining: percent_remaining(parts).round(6), text: format_vst(parts), precise_text: format_vst_precise(parts), day_ms: parts.day_ms.to_s, vick: parts.vick, pulse: parts.pulse, website_url: WEBSITE_URL, standard_link: standard_link } end |
.to_unix_ms(parts) ⇒ Object
254 255 256 |
# File 'lib/vishnu/standard_time.rb', line 254 def to_unix_ms(parts) VST_EPOCH_UNIX_MS + Integer(parts.day) * MS_PER_DAY + Integer(parts.day_ms) end |
.unix_ms_for(parts) ⇒ Object
247 248 249 250 251 252 |
# File 'lib/vishnu/standard_time.rb', line 247 def unix_ms_for(parts) return Integer(parts) if parts.is_a?(Integer) return Integer(parts.unix_ms) if parts.respond_to?(:unix_ms) && parts.unix_ms to_unix_ms(parts) end |
.until_flow(target_flow, from: local_now) ⇒ Object
403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
# File 'lib/vishnu/standard_time.rb', line 403 def until_flow(target_flow, from: local_now) target = Integer(target_flow) raise ArgumentError, "target flow must be from 0 through #{FLOWS_PER_DAY - 1}" if target.negative? || target >= FLOWS_PER_DAY current = Integer(from.flow) flow_delta = target >= current ? target - current : FLOWS_PER_DAY - current + target ms = duration_ms_for(flows: flow_delta) Duration.new( flows: flow_delta, milliseconds: ms, seconds: ms / 1000.0, target: after(flows: flow_delta, from: from) ) end |
.website_url ⇒ Object
198 199 200 |
# File 'lib/vishnu/standard_time.rb', line 198 def website_url WEBSITE_URL end |