Custom date and number format string (with padding and offsets) - php

I tried to create a customizable number according to a defined mask.
This is my rules to make a mask
You may enter any numbering mask. In this mask, the following tags could be used: {000000} corresponds to a number which will be incremented on each customer.
Enter as many zeros as the desired length of the counter.
The counter will be completed by zeros from the left in order to have as many zeros as the mask.
{000000+000} same as previous but an offset corresponding to the number to the right of the + sign is applied starting on first .
{000000#x} same as previous but the counter is reset to zero when month x is reached (x between 1 and 12).
If this option is used and x is 2 or higher, then sequence {yy}{mm} or {yyyy}{mm} is also required.
{dd} day (01 to 31).
{mm} month (01 to 12).
{yy}, {yyyy} or {y} year over 2, 4 or 1 numbers.
All other characters in the mask will remain intact.
Spaces are not allowed.
Example on customer created on 2007-03-01:
ABC{yy}{mm}-{000000} will give ABC0701-000099,
{0000+100}-ZZZ/{dd}/XXX will give 0199-ZZZ/31/XXX
So my current mask is C{000000}
<?php
$mask = "C{000000}";
$number = 100;
if (preg_match('/\{(0+)([#\+][0-9]+)?([#\+][0-9]+)?\}/i',$mask,$regType)){
$masktype=$regType[1];
$masktype_value=substr(preg_replace('/^TE_/','',$number),0,strlen($regType[1]));//get n first characters of code where n is length in mask
$masktype_value=str_pad($masktype_value,strlen($regType[1]),"#",STR_PAD_RIGHT);
$maskwithonlyymcode=$mask;
$maskwithonlyymcode=preg_replace('/\{(0+)([#\+][0-9]+)?([#\+][0-9]+)?\}/i',$regType[1],$maskwithonlyymcode);
$maskwithonlyymcode=preg_replace('/\{dd\}/i','dd',$maskwithonlyymcode);
$maskwithonlyymcode=preg_replace('/\{(c+)(0*)\}/i',$maskrefclient,$maskwithonlyymcode);
$maskwithonlyymcode=preg_replace('/\{(t+)\}/i',$masktype_value,$maskwithonlyymcode);
$maskwithnocode=$maskwithonlyymcode;
$maskwithnocode=preg_replace('/\{yyyy\}/i','yyyy',$maskwithnocode);
$maskwithnocode=preg_replace('/\{yy\}/i','yy',$maskwithnocode);
$maskwithnocode=preg_replace('/\{y\}/i','y',$maskwithnocode);
$maskwithnocode=preg_replace('/\{mm\}/i','mm',$maskwithnocode);
print "maskwithonlyymcode=".$maskwithonlyymcode." maskwithnocode=".$maskwithnocode."\n<br>";
}
?>
But it is not working it is printing
maskwithonlyymcode=C000000 maskwithnocode=C000000
My desired output is C000001 - C000100.
What is missing in this code?

I do not understand your code much, so I was not able to fix it, but what about:
<?
function process_mask($mask, $number, $date)
{
while (preg_match("/\{(.+?)\}/", $mask, $match))
{
$outter_code = $match[0];
$inner_code = $match[1];
if (preg_match("/^(0+)(\+(\d+))?$/", $inner_code, $match2))
{
$number2 = $number;
if (!empty($match2[3]))
{
$number2 += intval($match2[3]);
}
$replacement = str_pad($number2, strlen($match2[1]), "0", STR_PAD_LEFT);
}
else
{
switch ($inner_code)
{
case "dd":
$replacement = date("d", $date);
break;
case "mm":
$replacement = date("m", $date);
break;
case "y":
$replacement = substr(date("Y", $date), 3);
break;
case "yy":
$replacement = date("y", $date);
break;
case "yyyy":
$replacement = date("Y", $date);
break;
default:
trigger_error("Unrecognised code $inner_code");
return NULL;
}
}
$mask = str_replace($outter_code, $replacement, $mask);
}
return $mask;
}
function test_mask($mask)
{
$date = mktime(0, 0, 0, 4, 19, 2013);
echo str_pad($mask, 25)." => ".process_mask($mask, 100, $date)."\n";
}
test_mask("C{000}");
test_mask("C{000000}");
test_mask("C{000000+10}");
test_mask("ABC{yy}{mm}-{000000}");
test_mask("{0000+100}-ZZZ/{dd}/XXX");
?>
Outputs:
C{000} => C100
C{000000} => C000100
C{000000+10} => C000110
ABC{yy}{mm}-{000000} => ABC1304-000100
{0000+100}-ZZZ/{dd}/XXX => 0200-ZZZ/19/XXX
I absolutely do not undertand your rules about resetting counters. Based on what date do you want to reset the numbers? Current date? Do you keep some counter per customer (you have not explained what the number is)? Why resetting it on certain month? Wouldn't it be more meaningful to reset it in intervals? Like every month (implementation-wise, it would make sense then to keep separate counter for every month, so that the formatting logic is current time-independent). Some example may help understanding this.
Also for date formatting, I would suggest you to stick with PHP date formatting and do not invent your own.
I would suggest you to use pattern like this instead (It's actually bit .NET-like):
{(#[+offset]|php-date-format-string)[:length]}
So (for number = 999 and date = 2013-04-19):
C{#:4} => C0999
C{#+10:4} => C1009
C{#:6} => C000999
C{#:4}/{Y} => C0999/2013
C{#:4}/{Y:4} => C0999/2013
C{#:4}/{Y:2} => C0999/13
C{#:4}/{Y:1} => C0999/3
C{#:4}/{m} => C0999/03
C{#:4}/{Y}{m} => C0999/201303
C{#:4}/{Ym} => C0999/201303
Code for this would be way simpler, more extensible and flexible.

Related

Split 5 or 6 character number before fourth-last number

I get POST request date strings in the format of mY (no leading zeros on the month), so there's no delimiting character to split on.
Examples: 122019 or 62019
I need to separate the five or six digit string into a one or two digit month and a four digit year.
Eg1 : 122019
$a[0] = 12;
$a[1] = 2019
Eg2 : 62021
$a[0] = 6;
$a[1] = 2021
I don't know about this date format, especially about how you use an integer as a date. But let's consider that it's on purpose, the year is always gonna be 4 character, so you can just get the year by taking the last 4 char and use the rest as the month.
Using substr() see PHP.NET Substr
Return part of a string
And you can specify the start and length of the part you want to get, or using - to get character starting from the end of the string.
$weirdDate = 122019;
//takes the last 4 character
$year = substr($weirdDate , -4);
//takes the string from the beginning to 4 char before the end
$month = substr($weirdDate , 0,strlen($weirdDate)-4);
echo $year;
echo $month;
Again, it seems like a weird way to get a month/year date, but i'm answering based on the assumption that a year is gonna be 4 char long.
If it's not, you can't really split the number since the month part can be 1 or 2 char long.
09-2019 would be 92019
11-2019 would be 112019
A simple use of the substr() function will do this nicely
$in = '122019';
$year = substr($in,-4);
$month = substr($in,0, strlen($in)-4);
echo $year . ' month ' . $month;
$a[] = $month;
$a[] = $year;
RESULT
2019 month 12
array (
[0] => 12
[1] => 2019
)
Or if we use $in = '62019';
The RESULT would be
2019 month 6
array (
[0] => 6
[1] => 2019
)
Reference 'substr()`
You could also use substr with strpos. With substr(), you could first get the year by providing a negative offset to start capturing from back of the string as the year is going to be 4 digits. Then, you could use strpos() to find the index of the year and use this as the ending index to get the month.
That being said, best way to deal with this data is to either have a proper date format or better to have a JSON string with proper keys for days, month and year along with date.
Code:
<?php
$str = '122019';
$year = substr($str,-4);
$month = substr($str,0,strpos($str,$year));
echo $month," ",$year;
I have one another solution to use str_replace() with substr() like:
<?php
$string = "122019";
$year = substr($string, -4);
$date = str_replace($year, "", $string);
$myArray = array($date,$year); // convert into an array
echo "<pre>";
print_r($myArray);
?>
Desired Output:
Array
(
[0] => 12
[1] => 2019
)
Side Note: This will only work, if your year based on last 4 characters and other then these 4 characters must be date, as you mentioned in your question.
To accomplish this feat with two function calls, use negative parameters with substr() calls.
There is no reason to call strlen(), strpos(), or str_replace().
For a single call technique, use preg_split() with a lookahead pattern to ensure that no characters are consumed while exploding.
Codes: (Demo)
$mY = '62021';
var_export([
substr($mY, 0, -4), // month
substr($mY, -4) // year
]);
echo "\n---\n";
var_export(
preg_split('/(?=\d{4}$)/', $mY)
);

How to Format MP3 Length Time from String Like 00:00:00 in PHP

I store the length of an MP3 in MYSQL as a string formatted like 00:00:00 example: 01:23:15.
I'm trying to build a function to format that in a more user friendly way that removes hours,minutesas the case may be and do not show leading zeros or:`.
02:43:12 would become 2:45:12 (in this case the leading zero on the hours is removed)
02:03:12 would become 2:03:12 (in this case the leading zero on the minutes is needed visually because there are hours present)
00:18:28 would become 18:28 (in this case the leading zeros for hours and the : is removed)
00:08:28 would become 8:28 (in this case the leading zero on the minutes is not needed because there are no hours present)
00:00:14 would become 00:14 (in this case if minutes are zero then it should still show the two leading zeros so it's clear you're seeing seconds only)
00:00:04 would become 00:04 (in this case both the leading zeros for minutes and seconds show to make it more readable)
If you don't want to do lots of conditions you could also do it like this with simple str_replace() and a final check to remove leading 0 from 02:43:12
<?php
$tests = [
'12:43:23',
'02:43:12',
'02:03:12',
'00:18:28',
'00:08:28',
'00:00:14',
'00:00:04',
'00:00:00',
];
function mp3time($str) {
$str = str_replace(['00:0', '00:', '0:'], ['', '', '00:'], $str);
if (strlen($str) === 8 && $str[0] == 0) {
$str = substr($str, 1);
}
return $str;
}
foreach ($tests as $timestamp) {
echo mp3time($timestamp).PHP_EOL;
}
Result:
12:43:23
2:43:12
2:03:12
18:28
8:28
00:14
00:04
00:00
https://3v4l.org/H4A06
#Larence Cherones solution worked for me but I found 2 additional use cases that didn't work right. If the minutes were :00 or the seconds were :00 formatting messed up. Couldn't figure out a good way to modify his code to accommodate those cases so I came up with this hybrid of his solution.
function format_track_time($time) {
$a = substr($time, 0, 3); // hours
$b = substr($time, 3, 3); // minutes
$c = substr($time, 6, 2); // seconds
$a = str_replace(['00:'], [''], $a);
$time = $a . $b . $c;
// REMOVE LEADING ZERO FOR HOURS
if (strlen($time) == 8 && $time[0] == '0') {
$time = substr($time, 1);
}
// REMOVE LEADING ZERO FOR MINUTES IF NO HOURS
if (strlen($time) == 5 && $time[0] == '0') {
$time = substr($time, 1);
}
return $time;
}

Php check if digit time is greater than other digit time

I have an array of digit time numbers (I get them from the input fields).
Here are a few examples:
00:00:10
00:03:00
01:20:00
My question is following, how do I check if a digit time is greater than another?
Following function works up to
00:01:00
After that I get an error.
$inputTimes = $request->input('times', []);
foreach ($inputTimes as $inputKey => $inputValue)
{
$Input = new Input();
$Input->input_start = $inputValue['input_start'];
$tInput = explode(':', $inputValue['input_timelapse']);
$implInput = implode('', $tInput);
$iImplInput = (int)$implInput;
// Check if time is greater
if($iImplInput > $iVideoDuration)
{
.. error time greater
}
}
I would convert it to Unix time by using the inbuild strtotime() function.
For example:
strtotime('01:20:00'); will output 1483492800 while strtotime('00:01:00'); will output 1483488060.
Hope this helped.

Simple regex trying to extract 3 or 4 numbers from "dirty" time string

Despite some help earlier on I am still floundering in regex problems and now in array problems.
I am trying to allow users to put time in as 205pm 1405 14:05 2.05 pm and so on.
Previously I had times stored as 14:05 (standard mySQL TIME format) but users were not liking that but if I convert to 2:05 pm then, when the updated values are entered (in similar format), that obviously breaks the database.
I have NO TROUBLE going 14:05 to 2:05 pm but I am having a nightmare going in the opposite direction.
I have fudged things a bit with a cascading IF statement to get the string length but I have spent literally hours trying to get at the output.
IE if I get 2-05 pm, to start off with I just want to get 205.
Here is my atrocious code:
if ($_POST['xxx']='yyy')
{
$stuff=$_POST['stuff'];
$regex='/^\d\D*\d\D*\d\D*\d\D*\d\D*$/';
if (preg_match($regex, $stuff, $matches)) {echo " More than 4 digits. This cannot be a time."; }
else{
$regex='/^\d\D*\d\D*\d\D*\d\D*$/';
if (preg_match($regex, $stuff, $matches)) {echo " >>4 digits";}
else{
$regex='/^\d\D*\d\D*\d\D*$/';
if (preg_match($regex, $stuff, $matches)) {echo " >>3 digits";}
else{
$regex='/^\d\D*\d\D*$/';
if (preg_match($regex, $stuff, $matches)) {echo " Less than 3 digits. This cannot be a time.";}
}
}
}
}
debug ($matches,"mat1");
$NEWmatches = implode($matches);
debug ($matches,"matN1");
preg_match_all('!\d+!', $NEWmatches, $matches);
debug ($matches,"mat2");
$matches = implode($matches);
debug ($matches,"mat3");
echo "<br> Matches $matches"; /// I hoped to get the digits only here
?>
Thanks for any help.
$times = array(
'205pm', '1405', '4:05', '2.05 pm'
);
foreach($times as $time)
{
// parsing string into array with 'h' - hour, 'm' - minutes and 'ap' keys
preg_match('/(?P<h>\d{1,2})\D?(?P<m>\d{2})\s*(?P<ap>(a|p)m)?/i', $time, $matches);
// construction below is not necessary, it just removes extra values from array
$matches = array_intersect_key($matches,
array_flip(array_filter(array_keys($matches), 'is_string')));
// output the result
var_dump($matches);
}
If you are using that string at strtotime then it is easier just to reformat it to the correct format, like this
$times = array(
'205pm', '1405', '4:05', '2.05 pm'
);
var_dump(preg_replace('/(\d{1,2})\D?(\d{2})(\s*(a|p)m)?/i', '$1:$2$3', $times));
ps: for more complex possible situations I would suggest to reformat the time and do something like this, otherwise regexp can be a nightmare..
$times = array(
'9 pm', '205pm', '1405', '4:05', '2.05 pm'
);
$times = preg_replace('/(\d{1,2})\D?(\d{2})(\s*(a|p)m)?/i', '$1:$2$3', $times);
foreach($times as $time)
{
$date = strtotime($time);
if ($date === false) { echo 'Unable to parse the time ' . $time . "\n"; continue; }
$hour = date('G', $date);
$minutes = date('i', $date);
echo $hour . " : " . $minutes . "\n";
}
For your given example "2-05 or 14:05" you can use this RegEx:
^(?<HOUR>[0-9]{1,2})\s{0,}((-|:|\.)\s{0,})?(?<MIN>[0-9]{2})\s{0,}(?<MODE>(a|p)m)?$
"Hour" will hold the the first 2 numbers of the string, "MIN" will always hold the last 2 numbers of the string. "MODE" will hold (am or pm)
So you can combine them at the end to an single string. Also you can just run an simple Replace("-","").

Converting large numbers into letters (and back again)

Is there a term for the idea of storing large numbers as letters? For example let's say I have the (relatively small) number 138201162401719 and I want to shrink the number of characters (I know this does not help with saving disk space) to the fewest possible number of characters. There are 26 letters in the English alphabet (but i count them as 25 since we need a zero letter). If I start splitting up my large number into pieces that are each 25 or less I get:
13, 8, 20, 11, 6, 24, 0, 17, 19
If I then count the numbers of the alphabet a=0, b=1, c=2, d=3... I can convert this to:
NIULGYART
So I went from 15 digits long (138201162401719) to 9 characters long (NIULGYART). This could of course be easily converted back to the original number as well.
So...my first question is "Does this have a name" and my second "Does anyone have PHP code that will do the conversion (in both directions)?"
I am looking for proper terminology so that I can do my own research in Google...though working code examples are cool too.
This only possible if you're considering to store your number before processing as a string. Because you can't store huge number as integers. You will lost the precision (13820116240171986468445 will be stored as 1.3820116240172E+22) so the alot of digits are lost.
If you're considering storing the number as a string this will be your answer:
Functions used: intval, chr and preg_match_all.
<?php
$regex = '/(2[0-5])|(1[0-9])|([0-9])/';
$numberString = '138201162401719';
preg_match_all($regex, $numberString, $numberArray, PREG_SET_ORDER);
echo($numberString . " -> ");
foreach($numberArray as $value){
$character = chr (intval($value[0]) + 65);
echo($character);
}
?>
Demo
This is the result:
138201162401719 -> NIULGYART
Here's how I would do it:
Store the big number as a string and split it into an array of numbers containing one digit each
Loop through the array extract 2-digit chunks using substr()
Check if the number is less than 26 (in which case, it is an alphabet) and add them to an array
Use array_map() with chr() to create a new array of characters from the above array
Implode the resulting array to get the cipher
In code:
$str = '138201162401719';
$arr = str_split($str);
$i = 0; // starting from the left
while ($i < count($arr)) {
$n = substr($str, $i, 2);
$firstchar = substr($n, 0, 1);
if ($n < 26 && $firstchar != 0) {
$result[] = substr($str, $i, 2);
$i += 2; // advance two characters
} else {
$result[] = substr($str, $i, 1);
$i++; // advance one character
}
}
$output = array_map(function($n) {
return chr($n+65);
}, $result);
echo implode($output); // => NIULGYART
Demo.
As an alternative, you could convert the input integer to express it in base 26, instead of base 10. Something like (pseudocode):
func convertBase26(num)
if (num < 0)
return "-" & convertBase26(-num) // '&' is concatenate.
else if (num = 0)
return "A"
endif
output = "";
while (num > 0)
output <- ('A' + num MOD 26) & output // Modulus operator.
num <- num DIV 26 // Integer division.
endwhile
return output
endfunc
This uses A = 0, B = 1, up to Z = 25 and standard place notation: 26 = BA. Obviously a base conversion is easily reversible.
strtr() is a magnificent tool for this task! It replaces the longest match as is traverses the string.
Code: (Demo)
function toAlpha ($num) {
return strtr($num, range("A", "Z"));
}
$string = toAlpha("138201162401719");
echo "$string\n";
$string = toAlpha("123456789012345");
echo "$string\n";
$string = toAlpha("101112131415161");
echo "$string\n";
$string = toAlpha("2625242322212019");
echo "$string";
Output:
NIULGYART
MDEFGHIJAMDEF
KLMNOPQB
CGZYXWVUT
Just flip the lookup array to reverse the conversion: https://3v4l.org/YsFZu
Merged: https://3v4l.org/u3NQ5
Of course, I must mention that there is a vulnerability with converting a sequence of letters to numbers and back to letters. Consider BB becomes 11 then is mistaken for eleven which would traslate to L when converted again.
There are ways to mitigate this by adjusting the lookup array, but that may not be necessary/favorable depending on program requirements.
And here is another consideration from CodeReview.
I have been trying to do the same thing in PHP without success.
Assuming I'm using the 26 letters of the English alphabet, starting with A = 0 down to Z as 25:
I find the highest power of 26 lower than the number I am encoding. I divide it by the best power of 26 I found. Of the result I take away the integer, convert it to a letter and multiply the decimals by 26. I keep doing that until I get a whole number. It's ok to get a zero as it's an A, but if it has decimals it must be multiplied.
For 1 billion which is DGEHTYM and it's done in 6 loops obviously. Although my answer demonstrates how to encode, I'm afraid it does not help doing so on PHP which is what I'm trying to do myself. I hope the algorithm helps people out there though.

Categories