Date validation with the clock command - tcl

Consider the following code run on a Windows 7 system:
% info patchlevel
8.6.4
% clock scan "1995-01-35" -format "%Y-%m-%d"
791856000
% clock format [clock scan "1995-01-35" -format "%Y-%m-%d"] -format "%Y-%m-%d"
1995-02-04
%
I ran into this situation when trying to determine if a string was a valid date, I was expecting the first clock scan to fail as 35 isn't a valid day, but what happens is that I get a date 35 days after the 1st of January.
In my code I'm now comparing the output of the 2nd clock command to the original input and deciding that the string isn't really a valid date if the result is different.
Is there a better way to validate a date and is this the expected behaviour of the clock command, I can't find it described in the manual page for clock?

It is not in-built functionality in Tcl. If the dates/months are exceeding, then it is added to the successive days/months.
In your case, you are giving 35th of month January. The additional 4 days (i.e. 31 + 4 = 35) are added and calculated as month February's 4th day.
In the bizarre case that adding the given number of days yields a date
that does not exist because it falls within the dropped days of the
Julian-to-Gregorian conversion, the date is converted as if it was on
the Julian calendar.
Adding a number of months, or a number of years, is similar; it
converts the given time to a calendar date and time of day. It then
adds the requisite number of months or years, and reconverts the
resulting date and time of day to an absolute time.
If the resulting date is impossible because the month has too few days
(for example, when adding 1 month to 31 January), the last day of the
month is substituted. Thus, adding 1 month to 31 January will result
in 28 February in a common year or 29 February in a leap year.
proc is_valid_date {date {date_format "%Y-%m-%d"}} {
return [string equal [clock format [clock scan $date -format $date_format] -format $date_format] $date]
}
The date format is an optional and defaulted to %Y-%m-%d. If the format is not matching then it will fail. You can handle the exceptions.
Output :
% is_valid_date "1995-02-28"
1
% is_valid_date "1995-01-35"
0
%
We are converting the date to a long and reverting to the date again. If both are not same, then the given input is incorrect.
Reference : clock

Related

Tcl script to calculate a duration

I need to create a script whithin I can calcute a durations here an example :
assign duration1 5.hours
assign duration2 4.minutes
assign duration3 10.seconds
assign seconds [calc_duration [getVar duration1] + [getVar duration2] + [getVar duration3]]*
I want to get as a respons (seconds) for the example I would have "18250" seconds.
can you please help me with this.
Thanks.
The clock add command can do calculations with durations, but it needs a base time to work with. That's important because, for example, not all months are the same number of days long, and not all days are the same number of hours because of DST rules (which are themselves not the same every year). If you're only working with the small units and not too many of them, you can ignore this and use 0 (the start of the Unix epoch) as the base time.
set seconds [clock add 0 5 hours 4 minutes 10 seconds]
But if you're working with longer amounts of time, you'll need to be more careful. clock add still has the tools, but you'll have to choose the base timestamp correctly and specify your locale of interest.
# You're really supposed to scan with a format, but I'm lazy
set baseTimestamp [clock scan "4/4/2022 10:30 EST"]
set timezone :America/New_York; # EST/EDT
set timeBits {5 months 4 days 3 hours 2 minutes 1 second}
# Do the time arithmetic
set targetTimestamp [clock add $baseTimestamp {*}$timeBits -timezone $timezone]
# Convert into an elapsed number of seconds
set seconds [expr {$targetTimestamp - $baseTimestamp}]
# 13575721 seconds is a fair while...

calculating YearWeek from Tcl

I'm trying to correctly calculate working Year/Week number from a date field.
I tried to use the clock format command as follows
% clock format [clock scan "2016.01.01" -format "%Y.%m.%d"] -format "%Y-WW%V"
2016-WW53
and while the Week number is correct, the year is not because in this case Jan1 lands in week 53, as it did in 2016. Since the Tcl clock format command specifies the week number and the year separately, I end up with Jan 1, 2016 being shown as 2016-WW53 but this should be shown as 2015-WW53.
Is there a built in function for this or do I need to use an if/then condition to choose the year correctly?
Just use the modifier %G:
clock format [clock scan "2016.01.01" -format "%Y.%m.%d"] -format "%G-WW%V"
Output:
2015-WW53
From the manual:
%G
On output, produces a four-digit year number suitable for use with the week-based ISO8601 calendar; that is, the year number corresponds to the week number produced by %V. On input, accepts such a four-digit year number, possibly with leading whitespace.

How to interpret Timelien for HighStock

I am using the HighStock API of HighChart.
The demo:
http://jsfiddle.net/gh/get/jquery/1.9.1/highslide-software/highcharts.com/tree/master/samples/stock/demo/compare/
Makes a timeline on the x-Axis.
But the data JSON data for the graph has the following keys:
E.g. for AAPL:
?(/* AAPL historical OHLC data from the Google Finance API */
[
/* Dec 2008 */
[1229472000000,12.74],
[1229558400000,12.78],
[1229644800000,12.86],
[1229904000000,12.25],
[1229990400000,12.34],
[1230076800000,12.15],
[1230249600000,12.26],
[1230508800000,12.37],
[1230595200000,12.33],
[1230681600000,12.19],
How do all the 1229.... values relate to Date/Time?? I mean how does
1232582400000
relate to 22. January 2009???
And I have data int he following format in Java:
2015-12-10 15:27
How should I use them in the HighChart API?
The numeric time stamps you see as "1232582400000" is the javascript time stamp. I believe it is UNIX epoch time in milliseconds (multiply epoch time by 1000). You can convert your human-readable time values into javascript time in any number of ways. The basic example if you have year, month, day, hour, and second would be to make your data series use Date.UTC():
[Date.UTC(year, month, day, hour, minute), yValue]
"Unix time (also known as POSIX time or Epoch time) is a system for
describing instants in time, defined as the number of seconds that
have elapsed since 00:00:00 Coordinated Universal Time (UTC),
Thursday, 1 January 1970, not counting leap seconds."
(source)

getting time in milliseconds from a date

Is it possible to take a string of date, such as 2013-02-26 10:45:54,082 and get the time in milliseconds from the "epoch" ? or something similar?
I know clock scan returns the time in seconds.
Also clock scan returns an error for this specific format.
I couldn't find something as I want, and I want to make sure it doesn't exists before I start creating it...
Thanks
The millisecond-scale offset isn't supported by Tcl (as it is a fairly rare format in practice), but you can work around that:
set str "2013-02-26 10:45:54,082"
regexp {^([^\.,]*)[\.,](\d+)$} $str -> datepart millipart; # split on the last occurrence of , or .
set timestamp [clock scan $datepart -format "%Y-%m-%d %H:%M:%S" -gmt 1]
scan $millipart "%d" millis
set millistamp [expr {$timestamp*1000 + $millis}]
(You don't supply timezone info, so I've assumed it is UTC, i.e., “GMT”. I've also allowed for using . instead of , as a millisecond separator, as which you get depends on the locale that created the time string in the first place.)

Calculate date of birth from age at specific date [MySQL or Perl]

Apologies if this is a really simple question but I am interested in trying to reach an accurate answer and not just a "rounded" up answer.
My problem is: I know somebody is 27.12 on the 18th of March 2008 (random example). How can I calculate, to the nearest approximation, his date of birth. Age is always provided as a real number to two decimal points.
The solutions through simple fractional calculation are 1981-02-03 and the day before, due to rounding. As eumiro said, the resolution of 1/100 year is not precise enough, so it might still be off a day or two with the real date.
use DateTime qw();
use POSIX qw(modf);
my $date = DateTime->new(year => 2008, month => 3, day => 18); # 2008-03-18
my $age = 27.12; # 27.12
my ($days, $years) = modf $age; # (0.12, 27)
$days *= 365.25; # 43.83
# approx. number of days in a year, is accurate enough for this purpose
$date->clone->subtract(years => $years, days => $days); # 1981-02-03
$date->clone->subtract(years => $years, days => 1 + $days); # 1981-02-02
eumiro's answer does the trick; the following, using the Time::Piece module (bundled with Perl since 5.10) is perhaps more maintainable.
use strict;
use warnings;
use 5.010;
use Time::Piece;
use Time::Seconds;
my ($date, $age) = ('2008-03-18', 27.12);
my $birthday = Time::Piece->strptime($date, '%Y-%m-%d') - $age*ONE_YEAR;
say $birthday->ymd();
This will get you within a few days of the actual birthday, due to the lack of accuracy (1/100 year) in the age.
use strict;
use Time::Local;
my $now = timelocal(0, 0, 12, 18, 3-1, 2008);
my $birthday = $now - 27.12 * 365.25 * 86400;
print scalar localtime $birthday;
returns Mon Feb 2 22:04:48 1981.
Your precision is 0.01 year, which is roughly 3 days, so you even cannot cover all birthdays.
My method does not cover leap years very well, but you cannot really calculate exactly with them. Imagine the 01-March-2008. What date was "1 year and 1 day" before this date? 28-February-2007 or the not existing 29-February-2007?
A method that permits greater accuracy simply takes advantage of existing MySQL Date/Time functions. If working inside the MySQL, you can calculate the age with great precision by converting each of two dates to seconds in the TO_SECONDS() conversion and then manipulating the results to the desired precision. In these cases, the dates are in 'yyyy-mm-dd hh:mm:ss' formats and a year is assumed to have mean length of 365.242 days.
ROUND((TO_SECONDS(AnyDateTime) - TO_SECONDS(DateOfBirth))/(365.242*60*60*24),3) as age,
e.g.:
ROUND((TOSECONDS('2013-01-01 00:00:00') - TO_SECONDS('1942-10-16')/(365.242*60*60*24),3) as AGE --> 70.214
Alternatively you can use the DATEDIFF() conversion which provides the answer in days:
ROUND(DATEDIFF('2013-01-01 00:00:00','1942-10-16')/365.242,3) AS age --> 70.214