Parsing localized date strings in PHP - php

I have some code (it's part of a wordpress plugin) which takes a text string, and the format specifier given to date(), and attempts to parse it into an array containing hour, minute, second, day, month, year.
Currently, I use the following code (note that strtotime is horribly unreliable with things like 01/02/03)
// $format contains the string originally given to date(), and $content is the rendered string
if (function_exists('date_parse_from_format')) {
$content_parsed = date_parse_from_format($format, $content);
} else {
$content = preg_replace("([0-9]st|nd|rd|th)","\\1",$content);
$content_parsed = strptime($content, dateFormatToStrftime($format));
$content_parsed['hour']=$content_parsed['tm_hour'];
$content_parsed['minute']=$content_parsed['tm_min'];
$content_parsed['day']=$content_parsed['tm_mday'];
$content_parsed['month']=$content_parsed['tm_mon'] + 1;
$content_parsed['year']=$content_parsed['tm_year'] + 1900;
}
This actually works fairly well, and seems to handle every combination I've thrown at it.
However, recently someone gave me 24 Ноябрь, 2010. This is Russian for November 24, 2010 [the date format was j F, Y], and it is parsed as year = 2010, month = null, day = 24.
Are there any functions that I can use that know how to translate both November and Ноябрь into 11?
EDIT:
Running print_r(setlocale(LC_ALL, 0)); returns C. Switching back to strptime() seems to fix the problem, but the docs warn:
Internally, this function calls the strptime() function provided by the system's C library. This function can exhibit noticeably different behaviour across different operating systems. The use of date_parse_from_format(), which does not suffer from these issues, is recommended on PHP 5.3.0 and later.
Is date_parse_from_format() the correct API, and if so, how do I get it to recognize the language?

Try to set the locale to Russian as hinted in the manual:
Month and weekday names and other language dependent strings respect the current locale set with setlocale() (LC_TIME).

you could try take a locale parameter and call locale_set_default($locale) before doing the date parsing.
$originalLocale = locale_get_default();
$locale ? $locale : $originalLocale;
locale_set_default(locale);
// date parsing code
locale_set_default($originalLocale);
I haven't testing this but it's work a try.
FYI, I believe the locale string for Russian is "ru-Latn"

I see that the question is already answered but none of the solutions provided worked for me.
This is my solution:
if(!preg_match('/^en_US/', $locale)){
$months_short = array('jan' => t('jan'), 'feb' => t('feb'), 'mar' => t('mar'), 'apr' => t('apr'),
'may' => t('may'), 'jun' => t('giu'), 'jul' => t('lug'), 'aug' => t('ago'),
'sep' => t('set'), 'oct' => t('ott'), 'nov' => t('nov'), 'dec' => t('dec'));
foreach ($months_short as $month_short => $month_short_translated) {
$date = preg_replace('/'.$month_short_translated.'/', $month_short, strtolower($date));
}
}
$pieces = date_parse_from_format($format,$date);
if($pieces && $pieces['error_count'] == 0 && checkdate($pieces['month'], $pieces['day'], $pieces['year'])){
return date('Y-m-d', mktime(0,0,0,$pieces['month'],$pieces['day'],$pieces['year']));
}
Where t() returns the translated abbreviation for the month.
Probably not the best solution ever (because it fails if there is no valid translation) but it works for my case.

Related

How to get timestamp like this : 0000-00-00 00:00:00.000000 in php [duplicate]

I echo this :
php> echo date("Y-m-d\TH:i:s");
2011-05-27T11:21:23
How can do with date function to get this date format:
2011-01-12T14:41:35.7042252+01:00 (for example)
35.7042252 => seconds.decimal-fraction-of-second
I have tried:
php> function getTimestamp()
... {
... return date("Y-m-d\TH:i:s") . substr((string)microtime(), 1, 8);
... }
php> echo getTimestamp();
2011-05-27T15:34:35.6688370 // missing +01:00 how can I do?
date('Y-m-d\TH:i:s.uP')
u for microseconds was added in PHP 5.2.2. For earlier or (still) broken versions (see comments):
date('Y-m-d\TH:i:s') . substr(microtime(), 1, 8) . date('P')
Or, to avoid two calls to date:
date(sprintf('Y-m-d\TH:i:s%sP', substr(microtime(), 1, 8)))
Best performance:
substr_replace(date('c'), substr(microtime(), 1, 8), 19, 0);
By doing separate [date] calls you have a small chance of two time stamps being out of order: eg call date at 1:29:22.999999 and mircotime at 1:29:23.000001. On my server consecutive calls are about 10 us apart.
Source
Try this instead:
list($usec, $sec) = explode(" ", microtime());
echo date("Y-m-d\TH:i:s", $sec) . substr($usec, 1, 8) . date("P", $sec);
E.g.:
2015-07-19T16:59:16.0113674-07:00
As a solution for 2020 with the DateTime class, which was added in PHP 5.2, you can do a simple one liner to get the wanted format.
For example:
echo (new DateTime())->format('Y-m-d\TH:i:s.uP');
// 2020-04-23T09:18:25.311075+02:00
The DateTimeInterface, which is implemented by the DateTime class, brings its own constants for widely used date formats. If you know the format, you can use a constant for that.
echo var_dump($datetime->format(DateTime::RFC3339_EXTENDED));
// 2020-04-23T09:18:25.311+02:00
As object orientated programming is widely spread in the PHP world, this could be a possible solution, too.
If parsing the string returned by microtime makes you vomit in your mouth, and you don't want multiple distinct timestamps munged together into your output, you can do this:
$unow = microtime(true);
sprintf("%s.%06d%s", date("Y-m-d\TH:i:s", $unow), ($unow - floor($unow))*1e6, date("P", $unow));

Sort array of objects by closest date

I have an array of objects in PHP with one key - date. I have future dates as well as past dates. I need to sort them in by most current date.
Here is my array from var_dump()
array (size=15)
0 =>
object(stdClass)[7852]
public 'date' => string '20200417' (length=8)
1 =>
object(stdClass)[7846]
public 'date' => string '20200302' (length=8)
2 =>
object(stdClass)[7856]
public 'date' => string '20200224' (length=8)
3 =>
object(stdClass)[7853]
public 'date' => string '20200220' (length=8)
4 =>
object(stdClass)[7847]
public 'date' => string '20200213' (length=8)
5 =>
object(stdClass)[7845]
public 'date' => string '20200211' (length=8)
...
Is there a way to sort this so that the first object would be the 20200302, then 20200224 and so on. but the future dates would be at the very end of the array.
I've tried this:
usort($arr, function($a, $b) {
return strtotime($b->date) - strtotime($a->date);
});
But this code doesn't care about the current date, it just sorts them as dates.
I have also tried a workaround with calculating interval from the date('Ymd') - $arr->date and setting that as a part of an object, but strcmp() in usrot function gives virtually the same result, because future date will give negative number and in ascending order negative number will come before positive numbers. Meaning, the most future date will be first, not the most current date.
Any help is much appreciated.
What you need to do is work out how far each date is away from today and use this to sort the items, there may be a more elegant way, but this (I think) works. Basically use abs() to ensure all differences are +ve and take each date away from today...
usort($arr, function($a, $b) {
return (abs(strtotime('today') - strtotime($a->date))
- (abs(strtotime('today') - strtotime($b->date))));
});
Nigel's answer was a big help, but it produced uneven results, sometimes the dates would be all over the place, I have no idea why. So, I figured a workaround, perhaps it will be of help to someone on the internet.
I've added one more key/value to an object - interval, value for this key I calculated in the following manner.
$interval_raw = date("Ymd") - get_field('date_nachalo') + 1;
if($interval_raw < 0) {
$interval_raw += 10000;
}
So, if I get a negative number which is any future date, I'll add 10000 which is like 30 years in advance, a bit overkill I guess because no normal human schedules events that far into the future. Also, added + 1 so that the next upcoming date would be first. If I remove that, then the first date will be the date from the past. Basically, if today is march 10th and the last event was on march 1st and the next event event is on march 11th, without the + 1 the first event date would be the march 1st not the march 11th.
If anyone knows more elegant approach to this, do let the world know.

Parse string to timestamp [duplicate]

I have some code (it's part of a wordpress plugin) which takes a text string, and the format specifier given to date(), and attempts to parse it into an array containing hour, minute, second, day, month, year.
Currently, I use the following code (note that strtotime is horribly unreliable with things like 01/02/03)
// $format contains the string originally given to date(), and $content is the rendered string
if (function_exists('date_parse_from_format')) {
$content_parsed = date_parse_from_format($format, $content);
} else {
$content = preg_replace("([0-9]st|nd|rd|th)","\\1",$content);
$content_parsed = strptime($content, dateFormatToStrftime($format));
$content_parsed['hour']=$content_parsed['tm_hour'];
$content_parsed['minute']=$content_parsed['tm_min'];
$content_parsed['day']=$content_parsed['tm_mday'];
$content_parsed['month']=$content_parsed['tm_mon'] + 1;
$content_parsed['year']=$content_parsed['tm_year'] + 1900;
}
This actually works fairly well, and seems to handle every combination I've thrown at it.
However, recently someone gave me 24 Ноябрь, 2010. This is Russian for November 24, 2010 [the date format was j F, Y], and it is parsed as year = 2010, month = null, day = 24.
Are there any functions that I can use that know how to translate both November and Ноябрь into 11?
EDIT:
Running print_r(setlocale(LC_ALL, 0)); returns C. Switching back to strptime() seems to fix the problem, but the docs warn:
Internally, this function calls the strptime() function provided by the system's C library. This function can exhibit noticeably different behaviour across different operating systems. The use of date_parse_from_format(), which does not suffer from these issues, is recommended on PHP 5.3.0 and later.
Is date_parse_from_format() the correct API, and if so, how do I get it to recognize the language?
Try to set the locale to Russian as hinted in the manual:
Month and weekday names and other language dependent strings respect the current locale set with setlocale() (LC_TIME).
you could try take a locale parameter and call locale_set_default($locale) before doing the date parsing.
$originalLocale = locale_get_default();
$locale ? $locale : $originalLocale;
locale_set_default(locale);
// date parsing code
locale_set_default($originalLocale);
I haven't testing this but it's work a try.
FYI, I believe the locale string for Russian is "ru-Latn"
I see that the question is already answered but none of the solutions provided worked for me.
This is my solution:
if(!preg_match('/^en_US/', $locale)){
$months_short = array('jan' => t('jan'), 'feb' => t('feb'), 'mar' => t('mar'), 'apr' => t('apr'),
'may' => t('may'), 'jun' => t('giu'), 'jul' => t('lug'), 'aug' => t('ago'),
'sep' => t('set'), 'oct' => t('ott'), 'nov' => t('nov'), 'dec' => t('dec'));
foreach ($months_short as $month_short => $month_short_translated) {
$date = preg_replace('/'.$month_short_translated.'/', $month_short, strtolower($date));
}
}
$pieces = date_parse_from_format($format,$date);
if($pieces && $pieces['error_count'] == 0 && checkdate($pieces['month'], $pieces['day'], $pieces['year'])){
return date('Y-m-d', mktime(0,0,0,$pieces['month'],$pieces['day'],$pieces['year']));
}
Where t() returns the translated abbreviation for the month.
Probably not the best solution ever (because it fails if there is no valid translation) but it works for my case.

PHP print_r writes variable mystery?

I'm running into some very strange behavior with php in a webclient I'm working on. Essentially I'm passing an expiration date as a string which I'm parsing into a DateTime so I can compare it to the current time (to ascertain if something has expired or not.)
Heres the code snippet in question (NOTE: $expiration is set above this snippet, I'm just rewriting over it with the value I actually intend on using)
$expiration = DateTime::createFromFormat("y-m-d h:i:s", $expiration);
$now = date('y-m-d h:i:s', time());
Common::log("before : ", $expiration->date);
//TODO figure out why this common log has to be here or this doesnt work
Common::log("expiration : ", $expiration);
Common::log("after : ", $expiration->date);
if($now > $expiration->date) $data['status'] = 14;
Common::log is an in house function which is just doing print_r
static function log ($msg, $data=null)
{
error_log ($msg . ($data ? print_r ($data, true) : ''));
}
What that code snippet puts out into terminal (which is where I'm looking at whats getting print) is the following.
[09-Jun-2015 17:03:19 America/Indiana/Indianapolis] before :
[09-Jun-2015 17:03:19 America/Indiana/Indianapolis] expiration : DateTime Object
(
[date] => 2015-06-09 06:16:55
[timezone_type] => 3
[timezone] => America/Indiana/Indianapolis
)
[09-Jun-2015 17:03:19 America/Indiana/Indianapolis] after : 2015-06-09 06:16:55
if I simply comment out or delete the line thats logging the $expiration variable then, as the before : log shows, $expiration->date evaluates to the empty string and my logic doing the comparison below breaks. Whats going on here, why does taking out that middle log have any impact on the value of $expiration->date? This is extremely perplexing, and I would appreciate any help anyone can offer on this - I don't want to use code that works if I don't understand why it works.
To be honest I am not sure why this is happening. It probably has something to do with the class constructor. However, there is no documentation for using ->date this way. So, instead of doing things like this:
Common::log("before : ", $expiration->date);
do things this way:
Common::log("before : ", $expiration->format('y-m-d h:i:s'));
In other words you are telling PHP to display the date in the output format you choose (which could be different from the input format that you created it with).

preg_match for mysql date format

im trying to validate a date to see if it matchs the mysql format
this is the code
$match = "/^\d{4}-\d{2}-\d{2} [0-2][0-3]:[0-5][0-9]:[0-5][0-9]$/";
$s = $this->input->post("report_start"). " " . $this->input->post("report_start_time").":00";
$e = $this->input->post("report_end"). " " . $this->input->post("report_end_time").":59";
if($this->input->post("action") != "")
{
echo trim($s). " => " . preg_match($match, trim($s));
echo "<br>";
echo trim($e). " => " . preg_match($match, trim($e));
}
the date format goes into $s and $e are
$s = 2011-03-01 00:00:00
$e = 2011-03-01 23:59:59
and they both return false (0).
i tested the pattern on http://www.spaweditor.com/scripts/regex/index.php and it returns true (1)
http://pastebin.com/pFZSKYpj
however if i manual inter the date strings into preg_match like
preg_match($match, "2011-03-01 00:00:00")
it works.
i have no idea what im doing wrong
======================
now that i think about it, i only need to validate the houre:min part of the datetime string.
im manually adding the seconds and the date is forced by a datepicker and users cant edit it
You're making your work harder that it needs to be. In php there are many date handling functions that mean you don't have to treat dates like strings. So, rather than test that your input dates are in the correct format, just insist on the correct format:
$adate= date_create('January 6, 1983 1:30pm'); //date format that you don't want
$mysqldate= $adate->format("Y-m-d h:i:s");//date format that you do want
There are also functions to check that a date is a real date, like checkdate.
ok heres wat i did.
since im forcing the date format and the ending seconds of the time part
i just validated the hour:mini part using "/^2[0-3]|[01][0-9]:[0-5][0-9]$";
and if that returns true i put everything together end reconstructed the final datetime string
$match = "/^2[0-3]|[01][0-9]:[0-5][0-9]$/";
$s_d = $this->input->post("report_start");
$s_t = $this->input->post("report_start_time");
$e_d = $this->input->post("report_end");
$e_t = $this->input->post("report_end_time");
if($this->input->post("action") != "")
{
if(
( preg_match($match , trim($s_d." ".$s_t.":00")) )
&& ( preg_match($match , trim($e_d." ".$e_t.":59")) )
)
{
$r = $this->model_report->client_hours_logged(array($s,$e));
$data['report'] = $r;
var_dump($r);
//$this->load->view("report/client_hours_per_client",$data);
}
}
Watch out:
[0-2][0-3] is not a good regex for hour values - it will match 01, 12, 23 and others, but it will fail 04 through 09 and 14 through 19.
Better use (2[0-3]|[01][0-9]) instead.
I use this to validate a 'Y-m-d H:i:s' format date string:
match = '/^[12][0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[01]) ([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/';
You could use strtotime and date to parse and format the date properly.
Why not just simply force the date into the format you want:
$e = '2011-03-01 00:00:00';
$mysqlFormat = date('Y-m-d H:i:s', strtotime($e));
Also, there is a bit of an error in your regex [0-2][0-3]:[0-5][0-9]:[0-5][0-9] will only match the hours of 00,01,02,03,10,11,12,13,20,21,22,23 so it will never match 4am, or 3pm among others. That aside I looked over your RegEx and I don't see any problems with it matching the test cases you've offered. I would check to make sure there is not extra whitespace on either side of date string with trim().
I concur with Tim : MySQL behaves in quirks mode and always tries to go easy on DATE and DATE_TIME column types. You can omit certain parts of your input and it still will try to compensate and achieve that goal successfully to some degree... That's why, most numbers your Reg-ex considers as invalid, MySQL will accept as valid.

Categories