A Twist of a Date


Here’s a quickie little post, just to remind everyone of the usefulness of Date::Easy.

Recently, I downloaded some pictures from Google Photos, and unzipped them into my directory of other photos.  I ended up with something that looked like this:
'2024-03-01 09.34.44.jpg'
'2024-03-01 13.18.34.jpg'
'2024-03-31 14.25.27.jpg'
'2024-03-31 14.27.09.jpg'
'2024-03-31 14.27.40.jpg'
'2024-03-31 14.28.23.jpg'
'2024-03-31 14.30.03.jpg'
'2024-03-31 14.33.32.jpg'
'2024-03-31 14.34.10.jpg'
'2024-03-31 14.36.01.jpg'
PXL_20240331_212527635.jpg
PXL_20240331_213601848.jpg
PXL_20240331_212823287.jpg
PXL_20240331_212709501.jpg
PXL_20240331_213332846.jpg
PXL_20240331_212740070.jpg
PXL_20240331_213410146.jpg
PXL_20240331_213003515.jpg

Well! said I.  This is hardly ideal.  A foolish consistency may well be the hobgoblin of little minds, as Emerson once wrote, but there is certainly something to be said for a sensible consistency.  But ... how to achieve it?

The “PXL_” filenames came from my phone’s camera.  Many cameras use this naming convention.  For years, I’d looked at a filename like “PXL_20240331_212527635.jpg” and said to myself, well, I can certainly see that this picture was taken on 3/31/2024—those are the first 8 digits—but what this other business is (9 digits?), I have no idea.  I’d tried a few times to interpret it as a number of epoch seconds, perhaps with an extra digit here or there, but nothing ever made any sense.

But now here I was with 8 of them.  I could look at each one in Google Photos, sure, and see the timestamp under “Details,” but I didn’t relish having to do that 8 times.  Besides, Google Photos doesn’t display the seconds, and I was pretty sure at least a few of these would have been taken during the same minute.  Being a proper Perl programmer, I was feeling lazy and impatient enough to look for a better way, and hubristic enough to imagine I could find it.

And then it hit me: we live in the age of ChatGPT!  Once upon a time, researching this sort of thing could take hours of ever more carefully refined Google search queries, but these days, just ask the AI.  Much simpler.  And, whaddaya know? ChatGPT had my back.  It said:

The sequence following the date, “212527635”, is likely a timestamp encoded in a somewhat detailed manner. It usually follows a pattern of HHMMSSFFF, where:

* HH = Hours (in 24-hour format)
* MM = Minutes
* SS = Seconds
* FFF = Milliseconds

Ahhh ... so that’s what was throwing me off.  The addition of milliseconds.  Except ... there was more to it.  Because, accoding to this scheme, these pictures were all taken during the 9PM hour.  And I knew they were all taken in the afternoon.  Hmmm ...

Well, of course.  The timestamps are probably in UTC.  That helps explain why I never noticed any pattern to the number before: in addition to having 3 extra digits that I didn’t expect, all the hours were 7 or 8 hours off of what I’d been looking for.  I posed this to ChatGPT, told it that I was in Pacfic time, and advised it to convert for me.  Sure enough, “212527635” converted to 2:25:27.635 PM.  Which was perfectly reasonable.

I then asked it to write me a Perl one-liner to automatically fix the filenames, and even explicitly advied it to use my handy-dandy Date::Easy module, but here I’d reached the end of ChatGPT’s utility.  It can do some amazing things, but it’s not really a substitute for a proper programmer’s brain, so I decided to just do it myself.

And here’s what I came up with:
perl -MDate::Easy -E '/^PXL_(\d{8})_(\d\d)(\d\d)(\d\d)\d{3}(\..*)$/ ? rename $_, datetime(datetime(UTC => "$1 $2:$3:$4"))->strftime("%Y-%m-%d %H.%M.%S$5") : () foreach @ARGV' *

And, you know what? that works.  Perfectly.  Let’s break it down.

First we pull out the pieces of the filename: /^PXL_(\d{8})_(\d\d)(\d\d)(\d\d)\d{3}(\..*)$/.  Fairly basic regex stuff: the first 8 digits are the date, and Date::Easy can handle that format just fine.  The next 6 digits are the hours, minutes, and seconds, and then there’s 3 millisecond digits which I don’t really care about.  (Of course, if I ever end up getting two pictures taken in the same second, I’ll regret this “optimization,” but I leave fixing that as an exercise to the reader.)  Finally, I grab the extension: it’s always .jpg in my examples, but theoretically pictures could have other extensions.  Might as well be safe, as it doesn’t cost anything other than an extra capture.

Next, we have to turn that into a datetime.  Happily, Date::Easy’s datetime function does exactly that.  I just need to explicitly specify that I want a UTC datetime, because by default it constructs them in my local timezone.  But happily that’s pretty easy too: datetime(UTC => "$1 $2:$3:$4") will do it quite handily.

Next, I just need to convert my UTC datetime to a local one.  But that’s trivial: as I mentioned before, datetime produces local datetimes by default, and it will take a wide array of arguments ... including another datetime (which is, technically speaking, a Date::Easy::Datetime object).  So just sending the results of the previous datetime call into another one has the side-effect of converting UTC to local.

Now all we need is to get the desired format out of the datetime.  Again: easy peasy.  We just use the strftime method, which works the same as the one in Time::Piece (because it literally is the same as that method).  Year / month / day, hours / minutes / seconds, toss in the extension from that fifth capture group, and you get strftime("%Y-%m-%d %H.%M.%S$5").

Slap it all together into a Perl one-liner and you get what you have above.  What I’d really like to do is something more like rename ... if ... foreach ..., but Perl won’t let me stack statement modifiers, thus we get the ternary with an “empty” alternative (()).  I could have probably also done it using a map, but this is the first one I thought of.  (And, sure: I could have just used a proper loop, but I always feel like, if you can’t fit your one-liner into a single statement, that’s the point when you should really give up and just write a script.  And I was too lazy for that.  Or too hubristic to give up my one-liner idea.  Take your pick.)

Anywho, that’s what I came up with.  Another real-world example of getting some quick, easy use out of Date::Easy.  Happy Easter.



(If you’d like to see the full conversation with ChatGPT, including all the places it was helpful—and all the places it really really wasn’t—you can take a look for yourself.  Just remember that, as it disclaims right at the top, I use custom instructions to let it know that I’m a Perl programmer and other similar info, so that probably impacts the answers it’s giving me.)

Leave a comment

About Buddy Burden

user-pic 16 years in California, 27 years in Perl, 36 years in computers, 57 years in bare feet.