如何在TypeScript中处理日期字符串
发布时间:2025-05-17 03:29:55 发布人:远客网络
一、如何在TypeScript中处理日期字符串
在最近的一个项目中,我需要处理多种自定义的日期字符串格式,如YYYY-MM-DD和YYYYMMDD。TypeScript将这些字符串默认推断为string类型,但在实际工作中,这种泛化的类型定义使得处理日期字符串变得困难。例如,`let dog= alfie`也会被推断为string类型。我将介绍如何通过模板字面量类型和类型谓词来改进开发体验并减少错误。
模板字面量类型在TypeScript 4.1版本中引入,类似于JavaScript的模板字符串语法。它解析为给定模板中所有字符串组合的联合类型。例如:
type Person='Jeff'|'Maria'
type Greeting= `hi${Person}!`// Template literal type
const validGreeting: Greeting= `hi Jeff!`
const invalidGreeting: Greeting= `bye Jeff!`// Type'bye Jeff!' is not assignable to type'hi Jeff!| hi Maria!'
模板字面量类型强大且通用,允许对类型进行操作。例如:
type Person='Jeff'|'Maria'
type LoudGreeting= Uppercase// Capitalization of template literal type
const validGreeting: LoudGreeting= `HI JEFF!`
const invalidGreeting: LoudGreeting= `hi jeff!`// Type'hi Jeff!' is not assignable to type'HI JEFF!| HI MARIA!'
类型谓词有助于缩小类型范围。例如:
let age: string| number= getAge();
// `age` is of type `string`| `number`
if(typeof age==='number'){// `age` is narrowed to type `number`
} else{// `age` is narrowed to type `string`
在定义日期字符串时,我们首先需要创建模板字面量类型来表示所有类似日期的字符串的联合类型:
type oneToNine= 1| 2| 3| 4| 5| 6| 7| 8| 9
type zeroToNine= 0| 1| 2| 3| 4| 5| 6| 7| 8| 9
type YYYY= `19${zeroToNine}${zeroToNine}`| `20${zeroToNine}${zeroToNine}`
type MM= `0${oneToNine}`| `1${0| 1| 2}`
type DD= `${0}${oneToNine}`| `${1| 2}${zeroToNine}`| `3${0| 1}`
type RawDateString= `${YYYY}${MM}${DD}`
const date: RawDateString='19990223'// const dateInvalid: RawDateString='19990231'// 31st of February is not a valid date, but the template literal doesn't know!
const dateWrong: RawDateString='19990299'// Type error, 99 is not a valid day
模板字面量类型有助于指定日期字符串的格式,但没有实际验证日期。因此,编译器将`19990231`标记为有效日期,即使它是不正确的。我们可以通过设置名义类型`DateString`来改进这一点,使得有效的日期字符串类型更加具体:
const aDate: DateString='19990101';// Type'string' is not assignable to type'DateString'
为了确保`DateString`类型代表有效的日期,我们可以设置一个用户定义的类型保护来验证日期并缩小类型:
const isValidDate=(str: string): boolean=>{//...}
function isValidDateString(str: string): str is DateString{
return str.match(/^\d{4}\d{2}\d{2}$/)!== null&& isValidDate(str);
现在,我们可以将用户定义的类型保护应用于类型缩小,允许TypeScript编译器将类型细化为更具体的类型:
/*** Usage in type narrowing*/// valid string format, valid date
const date: string='19990223';
if(isValidDateString(date)){// evaluates to true, `date` is narrowed to type `DateString`}
/*** Usage in factory function*/
function toDateString(str: RawDateString): DateString{
if(isValidDateString(str)) return str;
throw new Error(`Invalid date string:${str}`);
/*** valid string format, valid date*/
const date1= toDateString('19990211');// `date1`, is of type `DateString`
const date2= toDateString('asdf');// Type error: Argument of type'"asdf"' is not assignable to parameter of type'"19000101"|...
/*** valid string format, invalid date(February doesn't have 31 days)*/
const date3= toDateString('19990231');// Throws Error: Invalid date string: 19990231
总结:本文介绍了如何在TypeScript中处理自定义日期字符串。这种方法也适用于其他自定义字符串,如用户ID、日期字符串等。通过结合用户定义的类型保护、模板字面量和名义类型,可以实现更强大的类型检查和更灵活的开发体验。
二、TypeScript实现字符串转树结构的方法详解
1、有一个多行字符串,每行开头会用空格来表示它的层级关系,每间隔一层它的空格总数为2,如何将它转为json格式的树型数据?本文就跟大家分享下这个算法,欢迎各位感兴趣的开发者阅读本文。例如有一个字符串:const text= `Language JavaScript TypeScript NodeJS HTMLServer DataBase MongoDBSystem Linux Window`;
2、将其转换为有层次结构的json数据后为:
3、{"name":"root","children":[{"name":"Language","children":[{"name":"JavaScript","children":[{"name":"TypeScript"},{"name":"NodeJS"} ]},{"name":"HTML"}]},{"name":"Server","children":[{"name":"DataBase","children":[{"name":"MongoDB"} ]}]},{"name":"System","children":[{"name":"Linux"},{"name":"Window"}]} ]}
4、乍一看,要对字符串进行处理,好像没有什么比较好的方法,理不出头绪。当我们遇到这种直接从数据结构出发想不出办法的问题时,这时可能就要换个思路了,能否将它转换为另一种数据结构呢?审题后发现,我们需要的数据元素在字符串中总是独占一行的,那么我们就要对每一行进行处理,此时最好的方式就是将它切割成数组。那么,我们就以换行符作为切割点来构造数组,如下所示:["","Language"," JavaScript"," TypeScript"," NodeJS"," HTML","Server"," DataBase"," MongoDB","System"," Linux"," Window",""]
5、观察数组中的每个元素后,我们发现最顶层的数据开头无空格,每间隔一层,开头就会多两个空格。按照从前往后的顺序依次读取数据,将后一个数据与其之前的数据进行比较,进而确定他们之间的层次关系。分析到这里,相信很多开发者已经看出了这个比较方式满足了后入先出原则,因此,我们可以用栈来解决这个问题,如下所示:准备2个栈,一个用于存放每层的字符串,另一个用于存放每层的空格数默认将root入栈,将它的空格数定为-1
6、接下来,我们将每个元素逐一入栈,分析下它的过程。如下图所示,我们列举了部分元素的入栈比对过程,通过观察后,总结出了如下几条规律。获取入栈元素的空格总数获取栈顶(deepStack)元素,判断入栈元素的空格总数是否大于栈顶元素。满足条件则获取strStack的栈顶元素,将入栈元素元素放入它的子级否则,将两个栈的元素依次出栈。直至入栈元素的空格总数比deepStack的栈顶元素大,获取strStack的栈顶元素,将入栈元素元素放入它的子级
7、将入栈元素以及它的空格总数分别放入对应的栈中直至所有元素都入栈比对完成,此问题得到解决注意:为了让读者更直观的看出规律,strStack栈中的元素用字符串直接代替了,实际上栈中存储的数据是一个对象,该对象包含了name属性和children属性。当前入栈元素也会构造成一个对象,得出栈顶元素(deepStack)与入栈元素空格总数的比对结果后,会将入栈元素对象放进栈顶元素(strStack)的children中。
8、经过上面的分析,我们已经得出了完整的实现思路,接下来我们来看下代码的实现。/***字符串转树结构*@param text*@constructor*/export function DataConversion(text: string): nodeObj{ const splitArr= text.split("\n"); const json={ name:"root"}; const strStack= new Stack(); const deepStack= new Stack(); strStack.push(json); deepStack.push(-1); for(let i= 0; i splitArr.length; i++){ let str= splitArr[i]; if(!str) continue;//获取空格总数 const len= str.lastIndexOf("")+ 1; str= str.replace(/\s/g,""); const curObj={ name: str};//寻找当前入栈元素的父层级 while(len= deepStack.peek()){ deepStack.pop(); strStack.pop();} const stackTop: nodeObj= strStack.peek(); stackTop.children? stackTop.children.push(curObj):(stackTop.children= [curObj]);//元素入栈,继续下一轮的比对 strStack.push(curObj); deepStack.push(len);} return json;}
9、注意:上述代码中声明了一个自定义类型nodeObj以及一个自定义类Stack,完整代码请在示例代码中查看。最后,我们将开头的例子代入上述代码中,校验下它能否正确解决问题。const text= `Language JavaScript TypeScript NodeJS HTMLServer DataBase MongoDBSystem Linux Window`;const textJSON= DataConversion(text);console.log(JSON.stringify(textJSON));