Ruby: How do I convert from UNIX struct tm? - ruby

I'm trying to work with Linux's RTC in Ruby. The RTC driver via ioctl returns soemthing very similar to struct tm, as found in the standard time.h file. Alas, I cannot find a standard Ruby method that understands this structure (month number is 0-based, year is 1900-based). Short of some trivial coding, is there a standard library/object in Ruby that can convert a tm struct/array into a Time object?
The current solution is:
rtctm_raw=rtc.unpack("iiiiii") # see rtc(4) or time.h
rtctm=[ *rtctm_raw, 0,0,0,0 ]
rtctm[4]+=1
rtctm[5]+=1900
rtc_values=Time.gm(*rtctm)
But I consider this ugly, since one would think Ruby's "gm" and "mktime" calls mirror the POSIX counterparts. But they don't. If such calls are available, I would prefer to use them.

If there's an offset, just apply it before creating a Time instance :
tm_struct = {
tm_year: 117,
tm_mon: 2,
tm_mday: 7,
tm_hour: 14,
tm_min: 32,
tm_sec: 30
}
puts Time.local(
tm_struct[:tm_year] + 1900,
tm_struct[:tm_mon] + 1,
*tm_struct.values_at(:tm_mday, :tm_hour, :tm_min, :tm_sec)
)
#=> 2017-03-07 14:32:30 +0100

Related

Micropython: How to use utime.mktime() without knowing the weekday/yearday?

I'm trying to convert an ISO time string like '2021-05-11T18:21:35Z' to an int (seconds from epoch), which mktime() does, except it seems strange to me that it requires the weekday and yearday as part of the argument. In general, it seems unlikely that you would know this, and in my situation I don't.
Obviously in python this is doable with things like datetime, but in uPython these don't exist and I haven't seen a non-external-library way to do this.
Much like with regular Python, the values of weekday and yearday are ignored (they get computed from the other values and are only accepted so that you can pass mktime the tuple returned by e.g. localtime).
You can run:
MicroPython v1.14 on 2021-03-07; ESP module with ESP8266
Type "help()" for more information.
>>> import time
>>> res = time.mktime((2021, 5, 11, 18, 21, 35, 0, 0))
>>> res
674072495
>>> time.localtime(res)
(2021, 5, 11, 18, 21, 35, 1, 131)

In Julia, how does one convert a list of ASCII decimals to a string?

tldr: I want to convert [125, 119, 48, 126, 40] to output string, }w0~(
To give a real life example, I am working with sequence data in fastq format (Here is a link to the library imported).
cat example.fastq outputs the following:
#some/random/identifier
ACTAG
+
}w0~(
The julia code below demonstrates reading the fastq file:
import BioSequences.FASTQ
fastq_stream = FASTQ.Reader(open("example.fastq", "r"))
for record in fastq_stream
# Still need to learn, why this offset of 33?
println(
Vector{Int8}(FASTQ.quality(record, :sanger)) .+ 33
)
println(
String(FASTQ.sequence(record))
)
println(
String(FASTQ.identifier(record))
)
break
end
close(fastq_stream)
This code prints the following:
[125, 119, 48, 126, 40]
ACTAG
some/random/identifier
I don't want to have to store this information in a list. I would prefer to convert it to string. So the output I am looking for here is:
}w0~(
ACTAG
some/random/identifier
julia> String(UInt8.([125, 119, 48, 126, 40]))
"}w0~("
Explanation
in Julia Strings are constructed using a set of bytes. If you are using ASCII only the char-byte mapping is simple and you can directly work on raw data (which is also the fastest way to do that).
Note that since Julia Strings are immutable, when creating String from raw bytes, the initial bytes become unavailable - this also means that no data is copied in the String creation process. Have a look at the example below:
julia> mybytes = UInt8.([125, 119, 48, 126, 40]);
julia> mystring = String(mybytes)
"}w0~("
julia> mybytes
0-element Array{UInt8,1}
Performance note
Strings in Julia are not internalized. In analytics scenarios always consider using Symbols instead of Strings. In some scenarios using temperature=:hot instead of temperature="hot" can mean 3x shorter execution time.
EDIT - performance test
julia> using Random, BenchmarkTools;Random.seed!(0);
bb = rand(33:126,1000);
julia> #btime join(Char.($bb));
31.573 μs (13 allocations: 6.56 KiB)
julia> #btime String(UInt8.($bb));
711.111 ns (2 allocations: 2.13 KiB)
String(UInt8.($bb)) is over 40x faster and uses 1/3 of the memory
I found a workable solution for now. I am sure there are more efficient solutions out there.
join(Char(i) for i in Vector{Int8}(FASTQ.quality(record, :sanger)) .+ 33) produces the output I require.

How to get range of dates that are representable/accepted by date(1)/mktime(3)?

On my modern 64bit Mac, the date 1901-12-14 is the earliest accepted by the following command:
date -ju -f "%F" "1901-12-14" "+%s"
I checked the source for the macOS date command here (Apple Open Source) and it is a failed mktime call that gives date: nonexistent time error for earlier dates.
I've looked over the source for mktime here (Apple Open Source) and I think that its a integer representation issue, but I'm not sure.
How can I find or compute the accepted range of dates of the date command (really of mktime)?
And if I wanted to get the Unix time for a date that can't be represented by mktime internals, what other libraries or functions can handle earlier dates?
The current macOS (OS X) implementation of mktime(3) has a minimum supported Unix time of INT32_MIN (-2147483648). This is because localtime.c:2102 assumes the result will fit in a 32-bit integer if the value is less than INT32_MAX:
/* optimization: see if the value is 31-bit (signed) */
t = (((time_t) 1) << (TYPE_BIT(int) - 1)) - 1;
bits = ((*funcp)(&t, offset, &mytm) == NULL || tmcomp(&mytm, &yourtm) < 0) ? TYPE_BIT(time_t) - 1 : TYPE_BIT(int) - 1;
Between 1901-12-14 and 1901-12-13 the Unix time dips below INT32_MIN, requires more than 32 bits, and is still less than INT32_MAX causing the function run out of bits.
This is not much of an issue considering that values before the year 1900 are explicitly disallowed by localtime.c:2073:
/* Don't go below 1900 for POLA */
if (yourtm.tm_year < 0)
return WRONG;
(POLA: Principle Of Least Astonishment)
Additionally, time_t is not required to be signed.

Converting String Object to Bytes and vice-versa using packer/unpacker Ruby

I want to convert a string object to bytes and Vice-versa.
Is it possible using Ruby Packer/Unpacker?
I am unable to find the format specifier to use
*pack_object = "Test".pack('**x**')* where x is format specifier
*unpacked_object = pack_object.unpack('**x**')* , this should result in "Test" string
String has a bytes method that returns an array of integers:
'Type'.bytes
#=> [84, 121, 112, 101]
The equivalent unpack directive is C*: (as already noted by cremno)
'Type'.unpack('C*')
#=> [84, 121, 112, 101]
Or the other way round:
[84, 121, 112, 101].pack('C*')
#=> "Type"
Note that pack returns a string in binary encoding.
Regarding your comment:
The output which i need is the same strung which i packed
pack and unpack are counterparts, so you can use all kind of directives:
'Type'.unpack('b*')
#=> ["00101010100111100000111010100110"]
['00101010100111100000111010100110'].pack('b*')
#=> 'Type'

How to compare two Time objects only down to the hour

I want to compare two Time objects only down to the hour, while ignoring the difference of minutes and seconds.
I'm currently using t0.strftime("%Y%m%d%H") == t1.strftime("%Y%m%d%H"), but I don't think this is a good solution.
Is there better way to do this?
You can use this trick in pure Ruby
t0.to_a[2..9] == t1.to_a[2..9]
Where Time#to_a
$> Time.now.to_a
# => [7, 44, 2, 8, 3, 2014, 6, 67, false, "GMT"]
# [ sec, min, hour, day, month, year, wday, yday, isdst, zone ]
So you can check that the times are equals or not up to the level you want and without missing important components of the object like the zone, etc.
If you have ActiveSupport (either through Rails, or just installed as a gem), it includes an extension to the Time class that adds a change method which will truncate times:
$> require "active_support/core_ext/time"
# => true
$> t = Time.now
# => 2014-03-07 21:30:01 -0500
$> t.change(hour: 0)
# => 2014-03-07 00:00:00 -0500
This won't modify the original time value either. So you can do this:
t0.change(minute: 0) == t1.change(minute: 0)
It'll zero out everything at a lower granularity (seconds, etc.).
require 'time'
t1 = Time.new ; sleep 30 ; t2 = Time.new
t1.hour == t2.hour
This should give you a boolean answer.

Resources