Maelstrom has asked for the wisdom of the Perl Monks concerning the following question:

So I'm looking to create a definable timestamp from the value of localtime and I realised I needed to zero pad several values to make the timestamps numerically sortable. My first attempt was
my($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(t +ime); $year += 1900; $month++; my @pad = qw (sec min hour day month); for (@pad) { $$_ = zeropad($$_); }
I went to run it and as usual perl is telling me it can't do something we both know it can. Tried other ways of dereferencing that should work like ${$_} and no dice. Wasted the next 20 minutes searching and like most internet searches relating to Perl all I can find is pages of long-winded, unasked for answers explaining that is bad and should never be done and and almost nothing in regards to if it can be done. What scant information I can find suggests it's possible only on global variables and only after I've turned of strict referencing which actually sounds really bad but only due to Perl sucking at variables rather than my solution. I'll probably use a hash but just once it would be nice to write some elegant code instead of having to refactor or kludge around autocracy.

Replies are listed 'Best First'.
Re: What's so wrong with this (dereferencing)code? (Symbolic Refs)
by LanX (Saint) on Jun 26, 2024 at 10:31 UTC
    You are trying to use symbolic references which are forbidden under strict refs

    In most cases it's better to just use hash values.

    hash slices make this easy:

    my %time; @time{ qw(sec min hour ...) } = localtime(time);

    Edit

    or

    my %time; my @pad = qw (sec min hour day month); @time{ @pad } = localtime(time);

    Please note how this is more DRY than your redundant code.

    The reason why symbolic references are deactivated by default "strictness" is that they result in very hard to spot errors.°

    If you really need this kind of meta programming, you can still reactivate it with no strict 'refs' within the local scope.¹

    Update

    And to why it doesn't work with private lexicals, I suppose it has to do with the history of Perl 4 to 5, the former didn't have strict or lexical vars.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    see Wikisyntax for the Monastery

    °) Especially people inexperienced to hashes are prone to this.

    FWIW: I've seen languages where this was the only way to emulate hashes, but Perl is not one of them.

    ¹) though you can already do all of this with %package:: stash or PadWalker or eval constructs

      I'm not 100% sure what you mean by DRY here, but I'll concede my code is only elegant from the perspective of the programmer not wanting to rewrite already written code rather than future readers. Now $_ = zeropad($_) for $sec, $min, $hour, $day, $month; OTOH? That's what I'm talking about! One added line, understandable at first glance and I've learnt a new construct. I'm also pleased to have learnt about hash slices of course but kind of overkill for solving this specific problem.
        DRY = Don't repeat yourself

        Symbolic references are just implicit hash lookups, doing them explicitly is better in the vast majority of cases.

        The for-solution you showed is aliasing which has similarities to referencing, but should not be confused.

        Update

        > Now $_ = zeropad($_) for $sec, $min, $hour, $day, $month;

        > OTOH? That's what I'm talking about!

        No you weren't.

        But FWIW, aliasing it's also feasible with hash values.

        Consider

        $_ = zeropad($_) for values %time

        and revaluate the clarity of concise code.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        see Wikisyntax for the Monastery

Re: What's so wrong with this (dereferencing)code?
by kikuchiyo (Hermit) on Jun 26, 2024 at 09:14 UTC
    If you want to generate timestamps, then generate timestamps:
    use POSIX qw( strftime ); my $timestamp = strftime('%Y-%m-%d %H:%M:%S', localtime);
      Timestamps are just one function of the function and I'm actually trying to deepen my knowledge of Perl rather than accomplish a specific task. For example why does this code work?
      use POSIX; $now = POSIX::strftime("%a, %e %b %Y %T GMT",gmtime($now));
      And this code doesn't?
      require POSIX; $now = POSIX::strftime("%a, %e %b %Y %T GMT",gmtime($now));
      I will be using strftime instead of rolling my own templates as even my frippery has it's limits but sometimes reinventing the wheel gets you a more efficient wheel. But once again I'm being bitten by Perls mind boggling inconsistency.

        The first snippet "works" because it is calling POSIX::gmtime whereas the second is calling CORE::gmtime. Different functions behave differently.


        🦛

        A reply falls below the community's threshold of quality. You may see it by logging in.
        POSIX docs warn that it imports everything by default, so either give it an import list, or give it an empty import list and fully qualify the functions:
        use POSIX 'strftime'; $now = strftime("%a, %e %b %Y %T GMT",gmtime($now)); use POSIX (); $now = POSIX::strftime("%a, %e %b %Y %T GMT",gmtime($now));
        Time::Piece docs say to use its version of strftime and strptime "without the overhead of the full POSIX extension".
Re: What's so wrong with this (dereferencing)code?
by etj (Priest) on Jun 26, 2024 at 11:46 UTC
    ${$varname} works by looking in the local package for that variable name. Since you've used my, your variables won't be found there.

    To achieve your stated immediate aim here of assigning to the variables you just assigned to (this "zero padding" is itself not a great idea, use the proper timestamp idea instead), you'd do:

    $_ = zeropad($_) for $sec, $min, $hour, $day, $month;
    But that itself won't affect the "timestamp" (how could it?). If you wanted a Unix timestamp with zeroes for those values, you'd just do something like:
    my ((undef) x 5, $year, $wday, $yday, $isdst) = localtime(time); my $zeroed_ts = timelocal((0) x 5, $year, $wday, $yday, $isdst);
      That line was exactly what I was looking for. Much elegance! As for affecting the time stamp I'm talking about using it an integer with no separators not a string. It currently returns as..
      get_date('timestamp'); 20240626204609 get_date('timestamp', { sep=>':' }); 2024:6:26:20:46:9
      I'm not doing any padding in the second case (although I note even the POSIX function pads with spaces by default). I could probably just use the output of time for the first case but it's human readable and happens to be the way I've always used time stamps when typing them by hand.
Re: What's so wrong with this (dereferencing)code?
by harangzsolt33 (Deacon) on Jun 27, 2024 at 11:38 UTC
    I'm looking to create a definable timestamp from the value of localtime and I realised I needed to zero pad several values to make the timestamps numerically sortable.

    If your goal is to sort dates, then you can actually skip the localtime conversion and just take "time" which gives you an integer, and pad it with zeros and sort that or just sort them as numbers. You don't have to create a timestamp or use localtime to break up the time into years, months, days, hours, minutes, and seconds. If your program saves the time by recording whatever "time" returns, then you can sort those integers very easily. You might not even have to zero pad them, just do:

    my @times = ... # list of times @times = sort {$a <=> $b} @times;

    Note: The "time" function returns the number of seconds since Jan 1, 1970 except on MacOS, where it returns the seconds since Jan 1, 1904.

      Note: The "time" function returns the number of seconds since Jan 1, 1970 except on MacOS, where it returns the seconds since Jan 1, 1904.

      Erm, no. Not for any Mac OS anyone's likely to be running this century.

      $ uname ; perl -E 'say time()' ; ssh bloop.local uname \; perl -E "'sa +y time()'" Darwin 1719488881 Linux 1719488881

      The cake is a lie.
      The cake is a lie.
      The cake is a lie.

        Sorry! I read this from an outdated perl manual. X_X
        Note: The "time" function returns the number of seconds since Jan 1, 1970 except on MacOS, where it returns the seconds since Jan 1, 1904.

      You're thinking of the timestamp for the OSX filesystem HFS+