Checking for time - validation

I have a form field which may have a date and may have a time in it. I need to confirm that both a date and time are present.
<input type="text" name="transdate" ... />
I can use isDate(form.transdate) to check if there is a date, but it does not check if there is a time. I wish there was a isTime() function.
Addendum
The date time fields can be made to have
These fields are concatinated via
date_cat = "#form.trans_date# #form.trans_date_h#:#form.trans_date_m# #form.trans_date_t#";
When I run this code:
cat: #date_cat# isValid(date): #isValid('date', date_cat)# isValid(time): #isValid('time', date_cat)#
I get
cat: 12/05/2018 :24 PM isValid(date): YES isValid(time): YES

Some people hate regular expressions. I love them. Why not just check the concatenated string?
dtRegEx = "^(0[1-9]|1[0-2])/(0[1-9]|[1-2][0-9]|3[0-1])/[1-9][0-9]{3} (0[0-9]|1[0-2]):[0-5][0-9] (am|pm)$";
if (reFind(dtRegEx, date_cat) and isDate(date_cat)) {
// valid datetime
} else {
// invalid datetime
}
RegEx Breakdown
^
string has to start with the whole pattern
(0[1-9]|1[0-2])
month in range from 01 to 09 or 10 to 12
/
date delimiter
(0[1-9]|[1-2][0-9]|3[0-1])
day in range from 01 to 09, 10 to 29 or 30 to 31
/
date delimiter
[1-9][0-9]{3}
year in range from 1000 to 9999
space
space, literally
(0[0-9]|1[0-2])
hour in range from 00 to 09 or 10 to 12
:
time delimiter
[0-5][0-9]
seconds in range from 00 to 59
space
space, again
(am|pm)
the meridiem stuff you guys from US and UK like so much :P
$
string has to end with the whole pattern
Note that the above pattern could still have you end up with invalid day ranges like 02/31/2018, that's why you should still check with isDate().

Here is how I addressed it, I validated the fields before the concatenation
if (form.trans_date_h == "" || form.trans_date_m == "" || form.trans_date_t == "") {
// error handling here
Then did the concatenation
date_cat = "#form.trans_date# #form.trans_date_h#:#form.trans_date_m# #form.trans_date_t#";

Related

Procmail filtering by Date: field

I need to move away mails older than given time - let it be 24h = 86400s. I use old good procmail for multiple other purposes on that machine, so I wanted to use is as well for this purpose. It also behaves well under the load (~1 000 000 small automated messages per day).
It took me a while to get to this ugly solution (excerpt from bigger procmailrc file):
Grab Date: field using formail
Grab current date in UNIX format (seconds)
bash convert the mail date to unix format
compare values using bash
return result to procmail using exit code.
Together:
MAILDATE_RFC=`formail -zxDate:`
DATE_UNIX=`date "+%s"`
:0
* ? MAILDATE_UNIX=`date -d "$MAILDATE_RFC" "+%s"` ; if ( (( ($DATE_UNIX-$MAILDATE_UNIX) > 86400)) ) then exit 0; else exit 1; fi
! account_for_outdated_mails
In this case I need to use the "Date:" field, as this contains the local time at which the mail was generated (it can take multiple days to get to my machine). We are 100% sure that "Date:" field exists and contains RFC-style date (those are automated messages in separated mail network).
My solution looks pretty ugly:
Getting the comparison result from bash using exit codes looks pretty bad. Might be inefficient as well.
I would like to calculate the MAILDATE_RFC still in procmail but it seems I cannot use any variable as the argument to generate another variable:
MAILDATE_UNIX=`date -d "$MAILDATE_RFC" "+%s"`
does not work.
The only optimization I am aware of would be to push the whole process of getting MAILDATE_RFC, MAILDATE_UNIX and DATE_UNIX processed in bash script and doing it in one bash session instead of 3.
My question: Is there a better way to do it? Maybe more efficient?
What you say doesn't work actually does. Here's a quick demo.
testing.rc:
DEFAULT=/dev/null
SHELL=/bin/sh
VERBOSE=yes
MAILDATE_RFC=`formail -zxDate:`
MAILDATE_UNIX=`date -d "$MAILDATE_RFC" "+%s"`
NOW=`date +%s`
:0
* 86400^0 ^
* $ -$NOW^0 ^
* $ $MAILDATE_UNIX^0 ^
{ LOG="score: $=
" }
Test run, in a fresh Ubuntu 20.04 Docker image:
tripleee#bash$ procmail -m testing.rc <<\:
> Subject: demo message
> Date: Fri, 10 Jun 2022 06:20:36 +0000
>
> Try me
> :
procmail: [263] Fri Jun 10 06:21:23 2022
procmail: Executing "formail,-zxDate:"
procmail: [263] Fri Jun 10 06:21:23 2022
procmail: Assigning "MAILDATE_RFC=Fri, 10 Jun 2022 06:20:36 +0000"
procmail: Executing "date,-d,Fri, 10 Jun 2022 06:20:36 +0000,+%s"
procmail: Assigning "MAILDATE_UNIX=1654842036"
procmail: Executing "date,+%s"
procmail: Assigning "NOW=1654842083"
procmail: Score: 86400 86400 "^"
procmail: Score: -1654842083 -1654755683 "^"
procmail: Score: 1654842036 86353 "^"
procmail: Assigning "LOG=score: 86353
"
score: 86353
procmail: Assigning "LASTFOLDER=/dev/null"
procmail: Opening "/dev/null"
Folder: /dev/null 68
This also demonstrates how to use scoring to do the calculation. It's perhaps somewhat intimidating, but saves an external process, and so should be more efficient than doing the calculation in Bash.
In some more detail, 123^0 regex says to add 123 to the score just once if the message matches the regex regex (in the recipe above, we use the regex ^ which of course always matches; every message contains a beginning. You could change the 0 to e.g. 1 to say to add for every match, or etc - see the procmailsc man page for proper documentation). The $ modifier says to expand any variables in the recipe itself.
If you are not using GNU date, you don't have date -d; in that case, probably refer to your platform's man page for how to calculate a date stamp for an arbitrary date. How to convert date string to epoch timestamp with the OS X BSD `date` command? has a discussion for MacOS, which should also work for any other *BSD platform.
If you really wanted to make this more efficient, and can be sure that the Date: header really always uses the RFC-mandated format, you could even parse the date in Procmail. Something like
:0
* ^Date: [A-Z][a-z][a-z], \/[ 0-9][0-9] [A-Z][a-z][a-z] [0-9][0-9][0-9][0-9]
{
date=$MATCH
:0
* date ?? ^\/[ 0-9][0-9]
{ dd=$MATCH }
:0
* date ?? ^[ 0-9][0-9] \/[A-Z][a-z][a-z]
{ mon=$MATCH }
* date ?? [A-Z][a-z][a-z] \/[0-9][0-9][0-9][0-9]
{ yyyy=$MATCH }
:0
* mon ?? 1^0 ^Jan
* mon ?? 2^0 ^Feb
* mon ?? 3^0 ^Mar
* mon ?? 4^0 ^Apr
* mon ?? 5^0 ^May
* mon ?? 6^0 ^Jun
* mon ?? 7^0 ^Jul
* mon ?? 8^0 ^Aug
* mon ?? 9^0 ^Sep
* mon ?? 10^0 ^Oct
* mon ?? 11^0 ^Nov
* mon ?? 12^0 ^Dec
{ mm=$= }
}
The \/ token in a regex says to save the matched text after it into the special variable MATCH. We then copy that variable to date and perform additional matching to extract its parts.
Performing the necessary arithmetic to convert this into seconds since January 1, 1970 should be doable at this point, I hope. If you need complete per-day accuracy, you would also need to extract the time and the time zone and adjust to the correct day if it's not in your preferred time zone, or perhaps UTC (that would be +0000 at the very end); but this is just a sketch, anyway, because I think I have a better idea altogether.
Namely, save the messages to the correct folder as they arrive, then just forward or discard or archive older folders when you no longer need them.
MAILDATE_RFC=`formail -czxDate:`
MAILDATE=`date -d "$MAILDATE_RFC" +%F`
:0:
inbox-$MAILDATE
This will save to an mbox file named like inbox-2022-06-10 based on the extracted Date: header. (Again, you could avoid the external processes if you really wanted to squeeze out the last bit of performance, using the date parsing sketch above. And again, if you can't have a message from a different time zone land in the previous or next day's folder, you need to recalculate the date for your time zone.)

Parse complex string to carbon date

I am using Laravel Framework 6.16.0 and want to parse a date with carbon "Dec 14 02:04 PM":
$fillingDate = "Dec 14 02:04 PM";
$filling_Date = Carbon::parse($fillingDate, 'UTC');
// result is only a string "Dec 14 02:04 PM"
When using the above structure I only get the string back. However, I would like to get a Carbon object that gets then formatted to ->format('Y-m-d').
Any suggestions what I am doing wrong?
I appreciate your replies!
You can create carbon object from a string by calling the static function createFromFormat.
In this case:
$fillingDate = "Dec 14 02:04 PM";
$filling_Date = Carbon::createFromFormat("M d g:i A", $fillingDate)
Format characters explained:
M - A short textual representation of a month, three letters
d - Day of the month, 2 digits with leading zeros
g - 12-hour format of an hour without leading zeros
i - Minutes with leading zeros
A - Uppercase Ante meridiem and Post meridiem
More about format characters: PHP documentation

Error parsing time string with timezone as "GMT+0000"

I am trying to parse "Tue Jun 11 2019 13:26:45 GMT+0000" with "Mon Jan 02 2006 15:04:05 MST-0700" as a layout string using time.Parse but I get this error parsing time "Tue Jun 11 2019 13:26:45 GMT+0000" as "Mon Jan 02 2006 15:04:05 MST-0700": cannot parse "" as "-0700".
I've been using above layout string for other offsets and it works fine. But, I think "+0000" is not being considered a valid offset or something? Any suggestions would be helpful.
EDIT: Using "Mon Jan 02 2006 15:04:05 GMT-0700" as the layout works and I get the output as 2019-06-11 13:26:45 +0000 +0000.
EDIT 2: According to reply in github issues, it turns out that in layout string "MST-0700" is actually two time zone value "MST" and numeric time zone value "-0700" which takes precedence over the former. And "GMT+08" or "GMT+00" is considered as a time zone value as a whole and matches against "MST". So it would be "GMT+08+0800" for "MST-0700".
I rarely touches time zone related problems, but I personally think this behaviour is confusing.
Original answer:
This is a bug confusing behaviour of Go's time library. While I am not certain of the desired behaviour of GMT time format (since I rarely see something like GMT+0800 or so in a time format string), the code logic that make GMT+0800 valid but GMT+0000 does not makes sense.
I have submitted an issue on the github: https://github.com/golang/go/issues/40472
In short, Go's time library parses format by consuming the layout string and the value string together. And currently when parsing a "GMT" time zone, it consumes more of the value string, if the following value string (say, "+0800" or "+0000") is signed and in range from -23 to +23. So not only does "GMT+0000" fails, but "GMT+0030" and "GMT+08" (when matching to `"MST-07") fails too.
Here is the detail: When parsing time zone, it checks the layout string and found "MST", so it tries to parse the time zone value in the value string, which at that time only has "GMT+0X00" (where X is '0' or '8') - others have been consumed (correctly). Source
case stdTZ:
// Does it look like a time zone?
if len(value) >= 3 && value[0:3] == "UTC" {
z = UTC
value = value[3:]
break
}
n, ok := parseTimeZone(value)
if !ok {
err = errBad
break
}
zoneName, value = value[:n], value[n:]
It's all good here. The parseTimeZone function makes a special case for GMT format for reason that is not obvious to me, but here is the code:
// Special case 2: GMT may have an hour offset; treat it specially.
if value[:3] == "GMT" {
length = parseGMT(value)
return length, true
}
And then parseGMT code is like this:
// parseGMT parses a GMT time zone. The input string is known to start "GMT".
// The function checks whether that is followed by a sign and a number in the
// range -23 through +23 excluding zero.
func parseGMT(value string) int {
value = value[3:]
if len(value) == 0 {
return 3
}
return 3 + parseSignedOffset(value)
}
From the comment, we can already sense problem: "+0800" is not a number in range from -23 to +23 excluding leading zero (while "+0000") is. And obviously, this function is trying to indicate (from the return value) to consume more than 3 bytes, that is more than the "GMT". We can confirm it in code of parseSignedOffset.
// parseSignedOffset parses a signed timezone offset (e.g. "+03" or "-04").
// The function checks for a signed number in the range -23 through +23 excluding zero.
// Returns length of the found offset string or 0 otherwise
func parseSignedOffset(value string) int {
sign := value[0]
if sign != '-' && sign != '+' {
return 0
}
x, rem, err := leadingInt(value[1:])
// fail if nothing consumed by leadingInt
if err != nil || value[1:] == rem {
return 0
}
if sign == '-' {
x = -x
}
if x < -23 || 23 < x {
return 0
}
return len(value) - len(rem)
}
Here, leadingInt is an uninteresting function that converts the prefix of a string to an int64, and the remaining part of the string.
So parseSignedOffset did what the documentation advertises: it parse the value string and see if it is in the range form -23 to +23, but at face value:
It considers +0800 as 800, larger than +23, so it returns 0. As a result, the parseGMT (and parseTimeZone) returns 3, so the Parse function only consumes 3 bytes this time, and leave "+0800" to match with "-0700", so it is parsed correctly.
It considers +0000 as 0, a valid value in the range, so it returns 5, meaning "+0000" is part of the time zone, and thus parseGMT (and parseTimeZone) returns 8, making the Parse function consumes the whole string, leaving "" to match with "-0700", and hence the error.
EDIT:
Using GMT in format string and get the "right" value is because that the "GMT" in format string is not considered as time zone, but a part of the format (just like spaces in the string), and "GMT" time zone is the same as the default time zone ("UTC").
You can time.Parse("Mon Jan 02 2006 15:04:05 XYZ-0700", "Tue Jun 11 2019 13:26:45 XYZ+0800") without getting an error.

Rails 4 parse a date in a different language

I have a text_field :birthday_line in my user form, that I need to parse into the user's birthday attribute.
So I'm doing something like this in my User class.
attr_accessor :birthday_line
before_save :set_birthday
def set_birthday
self.birthday = Date.strptime(birthday_line, I18n.translate("date.formats.default")
end
But the problem is that for some reason it gives me an error saying Invalid date when I try to pass in a string 27 января 1987 г. wich should be parsed to 1987-01-27.
The format and month names in my config/locales/ru.yml
ru:
date:
formats:
default: "%d %B %Y г."
month_names: [~, января, февраля, марта, апреля, мая, июня, июля, августа, сентября, октября, ноября, декабря]
seem to be correct.
Date.parse also doesn't help, it just parses the day number (27) and puts the month and year to todays date (so it'll be September 27 2013 instead of January 27 1987).
I had the same problem and what I can suggest:
string_with_cyrillic_date = '27 Января 1987'
1)create array of arrays like this
months = [["января", "Jan"], ["февраля", "Feb"], ["марта", "Mar"], ["апреля", "Apr"], ["мая", "May"], ["июня", "Jun"], ["июля", "Jul"], ["августа", "Aug"], ["сентября", "Sep"], ["октября", "Oct"], ["ноября", "Nov"], ["декабря", "Dec"]]
2) Now you can iterate this and find your cyrillic month:
months.each do |cyrillic_month, latin_month|
if string_with_cyrillic_date.match cyrillic_month
DateTime.parse string_with_cyrillic_date.gsub!(/#{cyrillic_month}/, latin_month)
end
end
And now you will receive the date that you expect
27 Jan 1987

How to check day is last date of month in ruby

I want to check if a day is the last day of the month and if it is, for a function to return true, otherwise return false.
For example, if I pass in an argument of "Sun, 30 Jun 2013", the function is to return true, because it is the last day of the month, however if I pass in the argument "Mon, 03 Jun 2013" the function is to return false.
How can this be accomplished using Ruby.
If you're using Rails, you can always do this as well:
date == date.end_of_month
or to check the end of this month:
date == Date.today.end_of_month
I would do something like this
def is_last_day(mydate)
mydate.month != mydate.next_day.month
end
Parse the date with DateTime.parse. DateTime.parse has built-in support for many date formats (including those in your example), but you can always use DateTime.strptime for more complex formats.
See if the next day is 1 (first day of next month) by using Date#+.
require 'date'
def last_day?(date_string)
date = DateTime.parse(date_string)
(date + 1).day == 1
end
puts last_day?('Sun, 30 Jun 2013') # true
puts last_day?('Mon, 03 Jun 2013') # false

Resources