I had been working on a QoL macro to convert selected text dates into a desired format. For that purpose I was trying to utilize the DateParse function that is referenced from the AHK archive forum. After going through all 7 pages of that thread none of the implementations seem to be working for me. It appears that regardless of my date string sent into the first RegEx expression a blank value is returned. Beyond that its still not clear to me why it would be returning today's date. The FormateTime function would return today's date but that function is not used in DateParse.
Any help to achieve the desired output of converting selected text to YYYY-MM-DD from an unknown date format would be appreciated.
Some inputs I have tested so far are:
20220202
2022/02/02
May 1, 2022
All return the current date from the DateParse function. I am using AHK version 1.1.30.2.
Here is the code that I am testing on as its own standalone script:
/*
Function: DateParse
Converts almost any date format to a YYYYMMDDHH24MISS value.
Parameters:
str - a date/time stamp as a string
Returns:
A valid YYYYMMDDHH24MISS value which can be used by FormatTime, EnvAdd and other timecommands.
Example:
> time := DateParse("2:35 PM, 27 November, 2007")
License:
- Version 1.05 <https://ahknet.autoh...ene/#dateparse>
- Dedicated to the public domain (CC0 1.0) <http://creativecommo...main/zero/1.0/>
*/
DateParse(str) {
static e2 = "i)(? :(\d{1,2}+)[\s\.\-\/,]+)?(\d{1,2}|
(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w*)[\s\.\-\/,]+(\d{2,4})"
str := RegExReplace(str, "((?:" . SubStr(e2, 42, 47) . ")\w*)(\s*)(\d{1,2})\b", "$3$2$1", "", 1)
If RegExMatch(str, "i)^\s*(? :(\d{4})([\s\-:\/])(\d{1,2})\2(\d{1,2}))?"
. "(?:\s*[T\s](\d{1,2})([\s\-:\/])(\d{1,2})(?:\6(\d{1,2})\s*(?:(Z)|(\+|\-)?"
. "(\d{1,2})\6(\d{1,2})(?:\6(\d{1,2}))?)?)?)?\s*$", i)
d3 := i1, d2 := i3, d1 := i4, t1 := i5, t2 := i7, t3 := i8
Else If !RegExMatch(str, "^\W*(\d{1,2}+)(\d{2})\W*$", t)
RegExMatch(str, "i)(\d{1,2})\s*:\s*(\d{1,2})(?:\s*(\d{1,2}))?(?:\s*([ap]m))?", t)
, RegExMatch(str, e2, d)
f = %A_FormatFloat%
SetFormat, Float, 02.0
d := (d3 ? (StrLen(d3) = 2 ? 20 : "") . d3 : A_YYYY)
. ((d2 := d2 + 0 ? d2 : (InStr(e2, SubStr(d2, 1, 3)) - 40) // 4 + 1.0) > 0
? d2 + 0.0 : A_MM) . ((d1 += 0.0) ? d1 : A_DD) . t1
+ (t1 = 12 ? t4 = "am" ? -12.0 : 0.0 : t4 = "am" ? 0.0 : 12.0) . t2 + 0.0 . t3 + 0.0
SetFormat, Float, %f%
Return, d
}
^!B::Reload
^`;::
clipSave := Clipboard
Clipboard = ; Empty the clipboard so that ClipWait has something to detect
SendInput, ^c ; copy selected text
ClipWait
StringReplace, Clipboard, Clipboard, `r`n, `n, All ; Fix for SendInput sending Windows linebreaks
msgbox Input is %Clipboard%
msgbox % "Result is " vDate := DateParse(Clipboard)
;vDate := Clipboard
;Msgbox % vDate
;vDate := DateParse("2022-02-02")
;Msgbox % vDate
;FormatTime, vDate, %test%, d
;Msgbox % vDate
;FormatTime, %myDate, ahk_now, yyyy-MM-dd
;send %myDate%
;Len:= Strlen(Clipboard) ;Set number of characters
;SendInput %Clipboard% ;Send new string
;SendInput +{left %Len%} ;Re-select text
VarSetCapacity(OutputText, 0) ;free memory
Clipboard := clipSave ;Restore previous clipboard
return
Answer
Going off the suggestion from T_lube I searched for a more direct conversion of certain formats and found another code sample (Albeiro & Mikeyww) that got me very close to the final result.
ConvDate(dateString, dateType := "", locale := "Month first")
{ ; Version .: 19-09-2020
; https://www.autohotkey.com/boards/viewtopic.php?f=76&t=80896
; From #mikeyww (modified Albireo)
;
; dateString - The date in some dateformat
;
; Convertformat .:
; dateType = 1 - handle .:
; - Tue Aug 11 13:59:27 2020 to (yyyymmdd - 20200811)
; locale := "Month first" Assume that the month is placed first in the string (default)
; eg.
; ConvDate(dt, TypeDate, locale)
; ConvDate("Tue Aug 11 13:59:27 2020", "1") => 20200811
; ConvDate("13.10.20") => 20201013
; ConvDate("20.07.03") => 20200703
; ConvDate("7.5.2020") => 20200705
; ConvDate("7.5.2020",,"Day first") => 20200507
; ConvDate("01192020") => 20200119
;
; ConvDate("19-8-2020") => 20200819 - Not affected by "Month first" or "Day first"
; ConvDate("2020.05.07") => 20200507 - Not affected by "Month first" or "Day first"
; ConvDate("19012020") => 20200119 - Not affected by "Month first" or "Day first"
; ConvDate("200928") => 20200928 - Not affected by "Month first" or "Day first"
; ConvDate("2020/10/05") => 20201005 - Not affected by "Month first" or "Day first"
; ConvDate("2020-10-05") => 20201005 - Not affected by "Month first" or "Day first"
; ConvDate("5 okt 2020") => 20201005 - Not affected by "Month first" or "Day first"
; ConvDate("05 okt 2020") => 20201005 - Not affected by "Month first" or "Day first"
; ConvDate("5 oktober 2020") => 20201005 - Not affected by "Month first" or "Day first"
; ConvDate("5-10-2020") => 20201005 - Not affected by "Month first" or "Day first"
; ConvDate("Okt 5, 2020") => 20201005 - Not affected by "Month first" or "Day first"
;
; ConvDate("20200119") => 20200119 - Not affected by "Month first" or "Day first"
; ConvDate("20200119") => 20200119 - Not affected by "Month first" or "Day first"
; ConvDate("01192020") => 20200119 - "Only first" or "Day first"
;
; ConvDate("10.8.20") ERROR => 1020 (- Input not handled)
; ConvDate("10.8.20",,"Day first") ERROR => 0820 (- Input not handled)
; ConvDate("8.19.2020",,"Day first") ERROR => 20201908 (- Wrong date input)
; ConvDate("01192020",, "Day first") ERROR => 20201901 (- Wrong date input)
; different test dates
; vDate := ConvDate("Tue Aug 11 13:59:27 2020", 1, "Month first")
; vDate := ConvDate("30 juli 2020",, "Day first")
; vDate := ConvDate("09-10-20",, "Day first") ; dd-mm-yy
; vDate := ConvDate("15-05-20",, "Day first") ; dd-mm-yy
; vDate := ConvDate("200820",, "Day first") ; ddmmyy
; vDate := ConvDate("200730") ; yymmdd
; vDate := ConvDate("200818") ; yymmdd
; vDate := ConvDate("200928") ; yymmdd
; vDate := ConvDate("20201013")
; vDate := ConvDate("20-05-06") ; yy-mm-dd
; vDate := ConvDate("20.07.03") ; yy.mm.dd
; vDate := ConvDate("18.08.2020",, "Day first")
; vDate := ConvDate("13.10.2020",, "Day first")
mos := { jan: 1
, feb: 2
, mar: 3
, apr: 4
, maj: 5
, may: 5
, jun: 6
, jul: 7
, aug: 8
, sep: 9
, okt: 10
, oct: 10
, nov: 11
, dec: 12}
yeard := False
part := []
new := []
dt := StrReplace(dateString, ",") ; Rensa bort alla "kommatecken"
If dateType
{ If dateType = 1
{ ; To Convert - Tue Aug 11 13:59:27 2020 - to (20200811)
locale := "Day first"
dt1 := StrSplit(dateString, A_Space)
dt := dt1.3 " " dt1.2 " " dt1.5
}
}
md := locale = "Month first" ? True : False
If RegExMatch(dt, "^\d{2}[-.]\d{2}[-.]\d{2}$")
dt := RegExReplace(dt, "[-.]")
If RegExMatch(dt, "^\d{6}$")
{ f2 := SubStr(dt, 1, 2)
If (f2 > 19)
dt := f2 + 2000 SubStr(dt, 3)
else
dt := SubStr(dt, 1, 4) Substr(dt, 5) + 2000
}
If RegExMatch(dt, "\d{8}")
ff := (SubStr(dt, 1, 4) > 1999) * 2,
. dt := SubStr(dt, 1, (ff + 3) - 1) "-"
. SubStr(dt, ff + 3, (ff + 5)- (ff + 3)) "-"
. SubStr(dt, ff + 5)
part := StrSplit(dt, [A_Space, "/", "-", "."]) ; Get year, month, day
For index, this in part
{ If this is alpha
{ For moName, moNum in mos
{ If SubStr(this, 1, 3) = moName ; Month matched a known string
part[index] := moNum, md := index = 1 ? True : False ; If string starts with alpha month, then day follows it
}
}
}
For index, this in part
{ If !RegExMatch(this, "\d{4}") ; This part is not the year
{ md := this > 12 ? (index > 1 ? True : False) : md ; For numbers > 12, if it's second, then month precedes it
new[new[3-md] ? md+2 : 3-md] := this ; Populate the month or day slot
} Else new[1] := this, md := index = 1 ? True : md ; If year comes first, then the month follows it
}
Return Format("{}-{:02}-{:02}", new[1], new[2], new[3])
}
I am fairly familiar with regex, and I do not think that some of those expressions will compile correctly (specifically "?:" within a literal string). Try using ErrorLevel by msgbox after each regexMatch call. I'd be willing to bet it is throwing errors silently about compile issues.
Were I in this position, I would figure out a limited number of formats that you are going to run into and simply check through each one with regexMatch. I think this would be much more maintainable code.
Related
I'm trying to create a procedure that puts "-" between different dates and "0" if the is single digit, but i'm having a very hard time not duplicating my code.
procedure put (Date : in Date_Type) is
begin
Put(Date.Y, Width => 1);
Put("-");
if Date.M <= 9 then
Put("0");
end if;
Put(Date.M, Width => 1);
Put("-");
if Date.D <= 9 then
Put("0");
end if;
Put(Date.D, Width => 1);
end put;
This is the best solution I came up with
An example of a nested procedure is:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Main is
subtype Year_Num is Integer range 1_900 .. 2_040;
subtype Month_Num is Integer range 1 .. 12;
subtype Day_Num is Integer range 1 .. 31;
type Date_Type is record
Y : Year_Num;
M : Month_Num;
D : Day_Num;
end record;
procedure Put (Date : Date_Type) is
procedure zerofill (Val : in Integer) is
begin
Put ("-" & (if (Val < 10) then "0" else ""));
Put (Item => Val, Width => 0);
end zerofill;
begin
Put (Item => Date.Y, Width => 0);
zerofill (Date.M);
zerofill (Date.D);
end Put;
A_Date : Date_Type := (2022, 12, 8);
begin
Put (A_Date);
end Main;
The nested nature of this answer is because the zerofill procedure is defined within the put procedure.
Came to this solution, I didnt duplicate my code but I somehow feel like I made it more complicated
procedure Zero(item : in integer) is
begin
Put("-");
if item < 10 then
Put('0');
end if;
Put(Item,Width =>0);
end Zero;
procedure put (Date : in Date_Type) is
begin
Put(Date.Y, Width => 0);
Zero(Date.M);
Zero(Date.D);
end put;
Hi in powerbi I am trying to create a list of dates starting from a column in my table [COD], and then ending on a set date. Right now this is just looping through 60 months from the column start date [COD]. Can i specify an ending variable for it loop until?
List.Transform({0..60}, (x) =>
Date.AddMonths(
(Date.StartOfMonth([COD])), x))
Assuming
start=Date.StartOfMonth([COD]),
end = #date(2020,4,30),
One way is to add column, custom column with formula
= { Number.From(start) .. Number.From(end) }
then expand and convert to date format
or you could generate a list with List.Dates instead, and expand that
= List.Dates(start, Number.From(end) - Number.From(start)+1, #duration(1, 0, 0, 0))
Assuming you want start of month dates through June 2023. In the example below, I have 2023 and 6 hard coded, but this could easily come from a parameter Date.Year(DateParameter) or or column Date.Month([EndDate]).
Get the count of months with this:
12 * (2023 - Date.Year([COD]) )
+ (6 - Date.Month([COD]) )
+ 1
Then just use this column in your formula:
List.Transform({0..[Month count]-1}, (x) =>
Date.AddMonths(Date.StartOfMonth([COD]), x)
)
You could also combine it all into one harder to read formula:
List.Transform(
{0..
(12 * ( Date.Year(DateParameter) - Date.Year([COD]) )
+ ( Date.Month(DateParameter) - Date.Month([COD]) )
)
}, (x) => Date.AddMonths(Date.StartOfMonth([COD]), x)
)
If there is a chance that COD could be after the End Date, you would want to include error checking the the Month count formula.
Generate list:
let
Start = Date1
, End = Date2
, Mos = ElapsedMonths(End, Start) + 1
, Dates = List.Transform(List.Numbers(0,Mos), each Date.AddMonths(Start, _))
in
Dates
ElapsedMonths(D1, D2) function def:
(D1 as date, D2 as date) =>
let
DStart = if D1 < D2 then D1 else D2
, DEnd = if D1 < D2 then D2 else D1
, Elapsed = (12*(Date.Year(DEnd)-Date.Year(DStart))+(Date.Month(DEnd)-Date.Month(DStart)))
in
Elapsed
Of course, you can create a function rather than hard code startdate and enddate:
(StartDate as date, optional EndDate as date, optional Months as number)=>
let
Mos = if EndDate = null
then (if Months = null
then error Error.Record("Missing Parameter", "Specify either [EndDate] or [Months]", "Both are null")
else Months
)
else ElapsedMonths(StartDate, EndDate) + 1
, Dates = List.Transform(List.Numbers(0, Mos), each Date.AddMonths(StartDate, _))
in
Dates
Doing a FindAllStringSubmatch regex match including named groups for different date formats. I am having trouble looping results. The commented out conditional makes it work, but not cleanly and it breaks as I add additional matches. I feel like I am approaching it wrong and would like some redirection. thx/
package main
import (
"fmt"
"regexp"
"strings"
)
func main() {
text := "12/31/1956 31/11/1960"
reg := []string{`(?P<month>1[12])/(?P<day>\d\d)/(?P<year>19\d\d)`, `(?P<day>\d\d)/(?P<month>1[12])/(?P<year>19\d\d)`}
// Combine them, case ignored
regall := "(?i)" + strings.Join(reg, "|")
myExp := regexp.MustCompile(regall)
match := myExp.FindAllStringSubmatch(text, -1)
fmt.Println("Match", match, len(match))
fmt.Println("Names", myExp.SubexpNames(), len(myExp.SubexpNames()))
for i := 0; i < len(match); i++ {
result := make(map[string]string)
for j, name := range myExp.SubexpNames() {
result[name] = match[i][j]
//if (result["month"] != "" && result["day"] != "" && result["year"] != "") {
fmt.Println(match[i][0],i,j,result["month"] + "/" + result["day"] + "/" + result["year"])
//}
}
}
}
Results in:
Match [[12/31/1956 12 31 1956 ] [31/11/1960 31 11 1960]] 2
Names [ month day year day month year] 7
12/31/1956 0 0 //
12/31/1956 0 1 12//
12/31/1956 0 2 12/31/
12/31/1956 0 3 12/31/1956
12/31/1956 0 4 12//1956
12/31/1956 0 5 //1956
12/31/1956 0 6 //
31/11/1960 1 0 //
31/11/1960 1 1 //
31/11/1960 1 2 //
31/11/1960 1 3 //
31/11/1960 1 4 /31/
31/11/1960 1 5 11/31/
31/11/1960 1 6 11/31/1960
After some rethinking and help from above, with aid of the above answer I came up looping the regex's separately to better handle overlapping named capture groups I came up with this: (posted for benefit of others and any critique is welcome)
package main
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
)
func main() {
month_d := `(?P<month>1[012]|0?[1-9])`
month_t := `(?P<month>jan(?:uary|.)?|feb(?:ruary|.)?|mar(?:ch|.)?|apr(?:il|.)?|may(.)|jun(?:e|.)?|jul(?:y|.)?|aug(?:ust|.)?|sep(?:tember|t|t.|.)?|oct(?:ober|.)?|nov(?:ember|.)?|dec(?:ember|.)?)`
day := `(?P<day>3[01]|[12][0-9]|[0]?[1-9])(?:\s)?(?:th|rd|nd|st)?`
year := `(?P<year>(?:19|20)?\d\d)`
sep_d := `[?:/.-]`
sep_t := `[?:,\s]`
text := "fedskjnkvdsj February 6 2004 sdffd Jan 12th 56 1/12/2000 2013/12/1 2099/12/5 1/12/1999 dsfjhfdhj"
regs := []string{
"(" + month_d + sep_d + day + sep_d + year + ")",
"(" + year + sep_d + month_d + sep_d + day + ")",
"(" + day + sep_d + month_d + sep_d + year + ")",
"(" + month_t + sep_t + day + sep_t + year + ")",
"(" + day + sep_t + month_t + sep_t + year + ")",
}
for i := 0; i < len(regs); i++ {
myExp, err := regexp.Compile("(?i)" + regs[i])
if err != nil {
fmt.Printf("There is a problem with your regexp.\n")
return
}
match := myExp.FindAllStringSubmatch(text, -1)
//fmt.Println("Match", match, len(match))
//fmt.Println("Names", myExp.SubexpNames(), len(myExp.SubexpNames()))
for j := 0; j < len(match); j++ {
result := make(map[string]string)
for k, name := range myExp.SubexpNames() {
result[name] = match[j][k]
}
// Fix Month
themonth := strings.ToLower(result["month"])
if len(themonth) == 1 {
themonth = "0" + themonth
}
if len(themonth) >= 3 {
themonth = themonth[0:3]
switch themonth {
case "jan":
themonth = "01"
case "feb":
themonth = "02"
case "mar":
themonth = "03"
case "apr":
themonth = "04"
case "may":
themonth = "05"
case "jun":
themonth = "06"
case "jul":
themonth = "07"
case "aug":
themonth = "08"
case "sep":
themonth = "09"
case "oct":
themonth = "10"
case "nov":
themonth = "11"
case "dec":
themonth = "12"
default:
fmt.Println("Month Error: " + themonth)
os.Exit(2)
}
}
// Day
theday := result["day"]
if len(theday) == 1 {
theday = "0" + theday
}
// Fix Year
theyear := result["year"]
if len(theyear) == 2 {
val_year, err := strconv.ParseInt(theyear,10,0)
if err != nil {
// handle error
fmt.Println(err)
os.Exit(2)
}
if val_year > 50 {
theyear = "19" + theyear
} else {
theyear = "20" + theyear
}
}
date := themonth + "/" + theday + "/" + theyear
fmt.Println(date)
}
}
}
i am having trouble with aligning outcome values.
Alist = ["1,25,999",
"123.4,56.7890,13.571",
"1,23.45,6,7.8"]
c = 0
while c < len(Alist):
r = 0
tokens = Alist[c].split(',')
while r < len(Alist[c].split(',')):
if '.' in tokens[r]:
print "%7.2f" %float(tokens[r]), " ",
else :
print "%3d" %float(tokens[r]), " ",
r += 1
print
c += 1
I want to print such as
1 25 999
123.40 56.79 13.57
1 23.45 6. 7.80
but somehow it is printing
1
25
999
123.40
56.79
13.57
1
23.45
6
7.8
and i cannot figure out what is wrong with my coding.
after the r+1, you have a lone print statement. it is at the wrong indention level - move it to the left by 4 spaces (or one tab) and it should work fine.
The print statement should'nt in the 2nd while loop. just:
Alist = ["1,25,999",
"123.4,56.7890,13.571",
"1,23.45,6,7.8"]
c = 0
while c < len(Alist):
r = 0
tokens = Alist[c].split(',')
while r < len(Alist[c].split(',')):
if '.' in tokens[r]:
print "%7.2f" %float(tokens[r]), " ",
else :
print "%3d" %float(tokens[r]), " ",
r += 1
print
c += 1
In [59]: %paste
myList = ["1,25,999",
"123.4,56.7890,13.571",
"1,23.45,6,7.8"]
rows = [r.split(',') for r in myList]
widths = {i:max(len(c) for c in col) for i,col in enumerate(itertools.izip_longest(*rows, fillvalue=""))}
for row in rows:
for i,val in enumerate(row):
print " "*((widths[i] - len(val))/2), val, " "*((widths[i] - len(val))/2) if not (widths[i]-len(val))%2 else " "*((widths[i] - len(val)+1)/2),
print
## -- End pasted text --
1 25 999
123.4 56.7890 13.571
1 23.45 6 7.8
In PL/SQL, is there any way to calculate next serial number from another one like 'A1B0010C'. Next serial no will be A1B0011C (+1). My idea is retrieve number part, increase it get return string back.
I can do this in java, but passing more than 1000 elements to Oracle will cause problems in IN clause.
So please help, any suggestion is appreciated.
Try to write some recursive function like this
This Function returns next character. ex: D after C. 0 after Z.
create or replace function get_next_character(ch char)
return char
is
result_ch char(1) := ch;
begin
IF UPPER(ch) = 'Z' or ch = '9' THEN
result_ch := 'A';
ELSE
result_ch := chr(ascii(ch) + 1);
END IF;
return upper(result_ch);
end get_next_character;
and this is your actual function which returns the next serial number
So, it generates 'A1B0010D' if your input is 'A1B0010C'
create or replace function get_next_serial(p_serial IN varchar2) -- PASS THE REQUIRED ARGUMENTS
return varchar2
IS
v_ret_serial varchar2(100);
v_in_serial varchar2(100) := p_serial;
tmp varchar2(100);
last_char char(1);
begin
tmp := v_in_serial;
for i in reverse 1..length(v_in_serial) loop
last_char := substr(tmp, length(tmp));
last_char := get_next_character(last_char);
tmp := substr(v_in_serial, 1, length(tmp)-1);
v_in_serial := substr(v_in_serial, 1, i-1) || last_char || substr(v_in_serial, i+1);
IF last_char <> 'A' then
exit;
END IF;
end loop;
IF last_char = 'A' THEN
v_in_serial:= 'A'||v_in_serial;
END IF;
return UPPER(v_in_serial);
exception
when NO_DATA_FOUND then
return 'AA';
when others then
return null;
end get_next_serial;
you can call this function (get_next_serial('abc')) where ever you want;
select get_next_serial('ALK0989KJ') from dual
You can place these two functions in a package and use at your convenience.
You can achieve it by using the following mix of Regexp_* functions:
SQL> with t1 as(
2 select 'A1B0010C' col from dual
3 )
4 select regexp_replace(col, '[[:digit:]]+'
5 , to_char(to_number(regexp_substr(col, '[[:digit:]]+',1,2) + 1), 'fm0000')
6 , 1
7 , 2
8 ) as Res
9 from t1
10 ;
RES
------------
A1B0011C
UPDATE In response to the comment.
SQL> with t1 as(
2 select 'A1B0010C' col from dual union all
3 select 'A1B0010C2' col from dual union all
4 select 'A1B0012C001' col from dual union all
5 select 'S000001' col from dual
6 )
7 select col
8 , regexp_replace(col
9 , '([[:digit:]]+)([[:alpha:]]+$|$)'
10 , LPad(to_char(to_number(num) + 1), length(num), '0') || '\2'
11 ) as Res
12 from (select col
13 , regexp_substr(col, '([[:digit:]]+)([[:alpha:]]+$|$)', 1, 1, 'i', 1) as num
14 from t1
15 )
16 ;
COL RES
----------- -----------------
A1B0010C A1B0011C
A1B0010C2 A1B0010C3
A1B0012C001 A1B0012C002
S000001 S000002
create or replace function nextSerial(s in varchar2) return varchar2 as
i integer;
done boolean := false;
c char;
newserial varchar2(4000);
begin
newserial := s;
i := length(newserial);
while i>0 and not done loop
c := substr(newserial, i, 1);
if c='9' then
newserial := substr(newserial, 1, i-1) || '0' || substr(newserial, i+1);
elsif c>='0' and c<='8' then
c := chr(ascii(c)+1);
newserial := substr(newserial, 1, i-1) || c || substr(newserial, i+1);
done := true;
end if;
i := i-1;
end loop;
if not done then
newserial := '1' || newserial;
end if;
return newserial;
end;