Get "at most" last n characters from string using PHP substr? - php

Answer of this question:
How can I get the last 7 characters of a PHP string? - Stack Overflow
How can I get the last 7 characters of a PHP string?
shows this statement:
substr($s, -7)
However, if length of $s is smaller than 7, it will return empty string(tested on PHP 5.2.6), e.g.
substr("abcd", -4) returns "abcd"
substr("bcd", -4) returns nothing
Currently, my workaround is
trim(substr(" $s",-4)) // prepend 3 blanks
Is there another elegant way to write substr() so it can be more perfect?
====
EDIT: Sorry for the typo of return value of substr("bcd", -4) in my post. It misguides people here. It should return nothing. I already correct it. (# 2016/1/29 17:03 GMT+8)

substr("abcd", -4) returns "abcd"
substr("bcd", -4) returns "bcd"
This is the correct behaviour of substr().
There was a bug in the substr() function in PHP versions 5.2.2-5.2.6 that made it return FALSE when its first argument (start) was negative and its absolute value was larger than the length of the string.
The behaviour is documented.
You should upgrade your PHP to a newer version (5.6 or 7.0). PHP 5.2 is dead and buried more than 5 years ago.
Or, at least, upgrade PHP 5.2 to its latest release (5.2.17)
An elegant solution to your request (assuming you are locked with a faulty PHP version):
function substr52($string, $start, $length)
{
$l = strlen($string);
// Clamp $start and $length to the range [-$l, $l]
// to circumvent the faulty behaviour in PHP 5.2.2-5.2.6
$start = min(max($start, -$l), $l);
$length = min(max($start, -$l), $l);
return substr($string, $start, $length);
}
However, it doesn't handle the cases when $length is 0, FALSE, NULL or when it is omitted.

In my haste with first comment I missed a parameter - I think it should have been more like this.
$s = 'look at all the thingymajigs';
echo trim( substr( $s, ( strlen( $s ) >= 7 ? -7 : -strlen( $s ) ), strlen( $s ) ) );

Related

Remove charactor/digit from string if string length greater than some value [duplicate]

This question already has answers here:
How do you pull first 100 characters of a string in PHP
(6 answers)
Closed 10 months ago.
using PHP, How can i remove the rest of character if sting_Len is greater than 6 for example i have 600275L and i want to end up like 600275 but only if greater than 6 digit.
i used the following code to to extract value starts with 600, i want update it to work for the above condition, Thank you
if((substr($key, 0, 3) == "600") && ($row['ItemClass']==3))
{
$assy = $key;
$rout = "Assy";
}
If you always want to limit it to six characters, then you should just be able to use substr for this without checking the length. If you write:
$string = 'abcdefg';
$string = substr($string, 0, 6);
then $string will equal 'abcdef'.
But if $string is shorter than 6 characters, it will just return the entire string. So if you write:
$string = 'abc';
$string = substr($string, 0, 6);
then $string will equal 'abc'.
You can see this in the PHP manual here.
Use the following logic
if(strlen($code) > 6) { echo substr($code, 0, 6); }

Check whether day is specified in a date string

Test case scenario - User clicks on one of two links: 2012/10, or 2012/10/15.
I need to know whether the DAY is specified within the link. I am already stripping the rest of the link (except above) out of my URL, am I am passing the value to an AJAX request to change days on an archive page.
I can do this in either JS or PHP - is checking against the regex /\d{4}\/\d{2}\/\d{2}/ the only approach to seeing if the day was specified or not?
You can also do this if you always get this format: 2012/10 or 2012/10/15
if( str.split("/").length == 3 ) { }
But than there is no guaranty it will be numbers. If you want to be sure they are numbers you do need that kind of regex to match the String.
You could explode the date by the "/" delimiter, then count the items:
$str = "2012/10";
$str2 = "2012/10/5";
echo count(explode("/", $str)); // 2
echo count(explode("/", $str2)); // 3
Or, turn it into a function:
<?php
function getDateParts($date) {
$date = explode("/", $date);
$y = !empty($date[0]) ? $date[0] : date("Y");
$m = !empty($date[1]) ? $date[1] : date("m");
$d = !empty($date[2]) ? $date[2] : date("d");
return array($y, $m, $d);
}
?>
I would personally use a regex, it is a great way of testing this sort of thing. Alternatively, you can split/implode the string on /, you will have an array of 3 strings (hopefully) which you can then test. I'd probably use that technique if I was going to do work with it later.
The easiest and fastest way is to check the length of the string!
In fact, you need to distinguish between: yyyy/mm/dd (which is 10 characters long) and yyyy/mm (which is 7 characters).
if(strlen($str) > 7) {
// Contains day
}
else {
// Does not contain day
}
This will work EVEN if you do not use leading zeros!
In fact:
2013/7/6 -> 8 characters (> 7 -> success)
2013/7 -> 6 characters (< 7 -> success)
This is certainly the fastest code too, as it does not require PHP to iterate over the whole string (as using explode() does).

substr return empty string

i have problem with $length of substr function
my CODE
$string='I love stackoverflow.com';
function arabicSubStr($value,$start,$length=false){
return mb_substr($value,$start,$length,'UTF-8');
}
echo arabicSubStr($string,7);//outputs nothing
echo substr($string,7);//outputs stackoverflow.com
The reason of the problem is:
If length is given and is 0, FALSE or NULL an empty string will be returned.
So, How i can fix the problem?
i won't use strlen($string)
EDITE
I know the reason is because i've defined $length as false
And i am here to know what should i put in $length parameter to avoid this error?
i am trying to put -1 it's returns //stackoverflow.co
Since the reason you're getting an empty string is specified entirely by the content of your question (using 0, FALSE or NULL), I assume you just want a way to get the rest of the string.
In which case, I'd use something like:
function arabicSubStr ($value, $start, $length = -1) {
if ($length == -1)
$length = mb_strlen ($value, 'UTF-8') - $start;
return mb_substr ($value, $start, $length, 'UTF-8');
}
You need to do it this way since there is no sentinel value of length that means "the rest of the string". Positive numbers (and zero) will limit the size to that given, negative numbers will strip off the end of the string (as you show in your question edit).
If you really don't want to use a string length function, you could try a value of 9999 (or even higher) and hope that:
the mb_substr() function will only use it as a maximum value; and
you won't pass in any strings 10K or more.
In other words, something along the lines of:
function arabicSubStr ($value, $start, $length = 9999){
return mb_substr ($value, $start, $length, 'UTF-8');
}
Though keep in mind I haven't tested that, I don't have any PHP environments at my current location.
It's because you have $length set to false as the default parameter for your function, which effectivley means you want it to return a substring of 0 length.
Unfortunately, if you have to set the final parameter (the charset) which I imagine you do, then you have to calculate the length of the string first, so something like:
function arabicSubStr($value,$start,$length=false){
$length = ($length) ? $length : mb_strlen($value,'UTF-8') - $start;
return mb_substr($value,$start,$length,'UTF-8');
}

How to compare version #'s inside a string?

I need to compare two version #'s to see if one is greater than the other and am having a really hard time doing so.
version 1: test_V10.1.0.a.1#example
version 2: test_V9.7.0_LS#example
I've tried stripping all non numeric characters out so I would be left with:
version1: 10101
version2: 970
Which drops the 'a' from 10.1.0.a.1 so that's no good, and I've tried taking everything between 'test_' and '#' then stripping out anything to the right of an underscore '_' and the underscore itself, but then I still have to strip out the 'V' at the beginning of the string.
Even if I can get down to just 10.1.0.a.1 and 9.7.0, how can I compare these two? How can I know if 10.1.0.a.1 is greater than 9.7.0? If I strip the decimals out I'm still left with a non numeric character in 1010a1, but I need that character in case say the release version I'm comparing this to is 10.1.0.b.1, this would be greater than 10.1.0.a.1.
This is driving me nuts, has anyone dealt with this before? How did you compare the values? I'm using php.
Shouldn't you be using? version_compare(ver1, ver2)
http://php.net/manual/en/function.version-compare.php
I think you want to consider working with a regex to parse out the "number" part of the version numbers - "10.1.0.a.1" and "9.7.0". After that, you can split by '.' to get two "version arrays".
With the version arrays, you pop elements off them until you find a higher number. Whichever array it came from is the higher version number. If either array runs out, it's a lesser version number (unless all the remaining elements are "0" or "a" or whatever semantics you use to say "base version", e.g., "10.0.0.a.0" == "10.0"). If both run out at the same time, then they're equal.
Use explode('.', $versionNum)
$ver1 = '10.1.0.a.1';
$ver2 = '10.1.0';
$arr1 = explode('.', $ver1);
$arr2 = explode('.', $ver2);
$min = min(count($arr1), count($arr2));
for ($i = 0; $i < $min; $i++)
{
if ($i + 1 == $min)
echo ($min == count($arr1)) ? $ver2 : $ver1;
if ($arr1[$i] > $arr2[$i])
{
echo $ver1;
break;
}
elseif ($arr1[$i] < $arr2[$i])
{
echo $ver2;
break;
}
}
The following regular expression will match everything between "test_V" and "#example" and throw it into an array called $matches[1]
$pattern = '/test_V(.*?)(?:_.*?)?#example/i';
$string = 'version 1: test_V10.1.0.a.1#example version 2: test_V9.7.0_LS#example';
if(preg_match_all($pattern,$string,$matches))
{
print_r($matches[1]);
}
returns
Array
(
[0] => 10.1.0.a.1
[1] => 9.7.0
)
This will give you a head start in figuring out how you want to pull apart your fairly complex version number.

Whats the cleanest way to convert a 5-7 digit number into xxx/xxx/xxx format in php?

I have sets of 5, 6 and 7 digit numbers. I need them to be displayed in the 000/000/000 format. So for example:
12345 would be displayed as 000/012/345
and
9876543 would be displayed as 009/876/543
I know how to do this in a messy way, involving a series of if/else statements, and strlen functions, but there has to be a cleaner way involving regex that Im not seeing.
sprintf and modulo is one option
function formatMyNumber($num)
{
return sprintf('%03d/%03d/%03d',
$num / 1000000,
($num / 1000) % 1000,
$num % 1000);
}
$padded = str_pad($number, 9, '0', STR_PAD_LEFT);
$split = str_split($padded, 3);
$formatted = implode('/', $split);
You asked for a regex solution, and I love playing with them, so here is a regex solution!
I show it for educational (and fun) purpose only, just use Adam's solution, clean, readable and fast.
function FormatWithSlashes($number)
{
return substr(preg_replace('/(\d{3})?(\d{3})?(\d{3})$/', '$1/$2/$3',
'0000' . $number),
-11, 11);
}
$numbers = Array(12345, 345678, 9876543);
foreach ($numbers as $val)
{
$r = FormatWithSlashes($val);
echo "<p>$r</p>";
}
OK, people are throwing stuff out, so I will too!
number_format would be great, because it accepts a thousands separator, but it doesn't do padding zeroes like sprintf and the like. So here's what I came up with for a one-liner:
function fmt($x) {
return substr(number_format($x+1000000000, 0, ".", "/"), 2);
}
Minor improvement to PhiLho's suggestion:
You can avoid the substr by changing the regex to:
function FormatWithSlashes($number)
{
return preg_replace('/^0*(\d{3})(\d{3})(\d{3})$/', '$1/$2/$3',
'0000' . $number);
}
I also removed the ? after each of the first two capture groups because, when given a 5, 6, or 7 digit number (as specified in the question), this will always have at least 9 digits to work with. If you want to guard against the possibility of receiving a smaller input number, run the regex against '000000000' . $number instead.
Alternately, you could use
substr('0000' . $number, -9, 9);
and then splice the slashes in at the appropriate places with substr_replace, which I suspect may be the fastest way to do this (no need to run regexes or do division), but that's really just getting into pointless optimization, as any of the solutions presented will still be much faster than establishing a network connection to the server.
This would be how I would write it if using Perl 5.10 .
use 5.010;
sub myformat(_;$){
# prepend with zeros
my $_ = 0 x ( 9-length($_[0]) ) . $_[0];
my $join = $_[1] // '/'; # using the 'defined or' operator `//`
# m// in a list context returns ($1,$2,$3,...)
join $join, m/ ^ (\d{3}) (\d{3}) (\d{3}) $ /x;
}
Tested with:
$_ = 11111;
say myformat;
say myformat(2222);
say myformat(33333,';');
say $_;
returns:
000/011/111
000/002/222
000;033;333
11111
Back-ported to Perl 5.8 :
sub myformat(;$$){
local $_ = #_ ? $_[0] : $_
# prepend with zeros
$_ = 0 x ( 9-length($_) ) . $_;
my $join = defined($_[1]) ? $_[1] :'/';
# m// in a list context returns ($1,$2,$3,...)
join $join, m/ ^ (\d{3}) (\d{3}) (\d{3}) $ /x;
}
Here's how I'd do it in python (sorry I don't know PHP as well). I'm sure you can convert it.
def convert(num): #num is an integer
a = str(num)
s = "0"*(9-len(a)) + a
return "%s/%s/%s" % (s[:3], s[3:6], s[6:9])
This just pads the number to have length 9, then splits the substrings.
That being said, it seems the modulo answer is a bit better.

Categories