Several years ago I had a discussion with a client who was worried about the year 2038 problem. Most UNIX systems are 32 bit operating systems. This means the maximum integer they can hold is 2,147,483,647. This means that the maximum date that can be held on one of those systems is Tuesday, January 19, 2038 3:14:07 AM (UTC). If you are smart enough to store your database timestamps as unsigned integers, that is extended to Sunday, February 7, 2106 6:28:15 AM (UTC) but only for your data, not for the OS.
There’s no need to run about with your hands flailing in the air. There is no reason that UNIX cannot run with a 64-bit system and no reason that UNIX servers will not be upgraded by the time 2038.01.19 rolls around.
However, that topic of discussion came around to survivability of data. How could we store dates in a manner that was not dependent on such limitations? The obvious answer was to store dates as a string. Storing dates as strings come with its own problems. So the challenge I had was to come up with a way of making dates storable as strings, yet still able to function as numbers.
This brought me back to the heady days of my high school computer class where I learned to write BASIC on an Atari 400. 😎 My end of year project was using combinations of keystrokes to print out representations of molecule chains for a variety of Carbon-based molecules. 😎😎
Alas, that high school computer class was the last formal computer class I ever took. But I rememberd discussions of COBOL and FORTRAN; it got me to thinking. FORTRAN was the first language to use single precision floating point numbers. Even on a 32 bit system, a floating point number can have a maximum value of 3.402823 x 1038. Yeah, okay, if I have any programs still running then I’ll be long past worrying about the dates being handled correctly.
So dusting off my memories of those high school discussions (over 30 years ago … ahem) I did a bit of research, then had several large coffees while I cogitated upon the problem and the rumblings of a solution.
That was how I came up with the M100 solution: express the date as a floating point number. For example, I’m writing this post on October 14th, 2017 at 03:57 am. So, under M100, I would write this date as:
The format is:
[4-digit year] & [month + 100] & [2 digit day] . [2 digit hours] & [2 digit minutes] & [2 digit seconds]
By storing dates as a floating point number (which I actually store as strings) I can do greater-than and less-than comparisons like I do with a UNIX timestamp. This means that I can also sort these dates and times ascending or descending. To compare if two y/m/d are equal, I just take the integer portion of the number and compare them for equality.
This meant, however, that I lost the ability to add and subtract dates. Hmmm. Perl module to the rescue. I wrote a module called M100.pm and included methods that would do just that.
This leads me to the next problem: location of the server. At the time I lived in Toronto and the server I was using was in California (UTC – 8). The server time was never the same as my time (UTC – 5). This meant that I had to standardize how these dates were stored and be able to easily process them for MY local time and not the server’s local time. That led to the decision to have the module process all dates as GMT (now called UTC). If this was going to be really useful, however, I need to make sure that the end user didn’t have to worry about converting their local time to UTC. Without getting into the boring blah, blah, blah about that, how about a demo?
Click here to see a demo of what the M100 will put out. Enter a date only, the demo uses the current time (in Toronto) with the date but that is something you would specify yourself with the module.
Using the module
Drop the module in the same directory as the script you want to use it with, then just use it like any other module:
After that, you instantiate the object, then override the local_offset value if you have to (you’ll have to run a test to see if you need to).
use M100; my $obj = M100->new; $obj->tz_modifer(-5);
After you feed a date to the object then you have several ways to manipulate it. Here is a full list of methods and procedures:
|new||Instantiates the object|
|local_offset||Read||Difference between your server and UTC.|
|epoch([input])||Write||Initialize the object with an epoch time.|
|nowtime||Read||Seconds since epoch modified by local_offset or tz_modifier|
|timestamp||Read||Classic timestamp output with 24 hour clock, eg. 2017-09-14T03:00:05-04 DST|
|m100([input])||Read/Write||Returns date and time in M100 format (yyyy1mmdd.hhmmss). Can accept input in multiple format.|
|You can pass values to m100() in the following format:
$obj->m100(yyyy, mm, dd, hh, mm, ss);
$obj->m100(yyyy, mm, dd); - Uses current time held by object
$obj->m100(hhmmss); - Uses current date held by object
$obj->m100(hh, mm, ss); - Uses current date held by object
$obj->m100(nnnnnnnnn); - Epoch time
|weekday||Read||Returns a 0 based list starting with Monday|
|This method uses a math calculation to determine DST. It does not rely on the DST setting in Perl's localtime(). The reason is that proper representation of DST in the returned array use to be very unreliable. For more info on calculating DST see:
The locations currently supported are "North America" (default), "Mexico", & "Europe"
|compvaldate||Read||Returns the date with 24 hour clock in the format yyyy1mmdd|
|compvaltime||Read||Returns the time in the format hhmmss.|
|printdate||Read||Prints out the ymd in International format (ISO8601)|
|convert_hours_and_minutes_to_decimal([hour, [minute]])||Function||Returns decimal value of the input hours and minutes|
|printtime_12hour||Read||Returns the time with 12 hour clock and "am" or "pm" appended.|
|printtime_long||Read||Returns time in 24 hr clock as hh:mm:ss|
|m100_add([n, [period]])||Function||Modifies objects held value; Period can be 'days', 'weeks', 'months', 'years'|
|m100_subtract([n, [period]])||Function||Modifies objects held value; Period can be 'days', 'weeks', 'months', 'years'|
|m100_differenceindays([low m100 value, [hi m100 value]])||Function||Returns integer value for the difference between two dates.|
|m100_differenceinhms([low m100 value, [hi m100 value]])||Function||Returns difference between two m100 values as h:mm:ss|
Download the files to get started!
|You can run the test script here|
*NOTE: The Perl script downloads as a .txt file to keep your browser from pitchin’ a hissy. Just save it with a .pl extension.
**NOTE: You will need the two CSS files for uploadtest.pl to display correctly. Change the path to them in that file.
Now go, code, be happy. 🤓