371 lines
11 KiB
Kotlin
371 lines
11 KiB
Kotlin
package io.legato.kazusa.utils
|
||
|
||
import android.annotation.SuppressLint
|
||
import android.text.TextUtils.isEmpty
|
||
import android.util.Base64
|
||
import java.io.ByteArrayInputStream
|
||
import java.io.ByteArrayOutputStream
|
||
import java.io.IOException
|
||
import java.text.DecimalFormat
|
||
import java.text.SimpleDateFormat
|
||
import java.util.Calendar
|
||
import java.util.Locale
|
||
import java.util.regex.Matcher
|
||
import java.util.regex.Pattern
|
||
import java.util.zip.GZIPInputStream
|
||
import java.util.zip.GZIPOutputStream
|
||
import kotlin.math.abs
|
||
|
||
|
||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||
object StringUtils {
|
||
private const val HOUR_OF_DAY = 24
|
||
private const val DAY_OF_YESTERDAY = 2
|
||
private const val TIME_UNIT = 60
|
||
private val ChnMap = chnMap
|
||
private val wordCountFormatter by lazy {
|
||
DecimalFormat("#.#")
|
||
}
|
||
|
||
private val chnMap: HashMap<Char, Int>
|
||
get() {
|
||
val map = HashMap<Char, Int>()
|
||
var cnStr = "零一二三四五六七八九十"
|
||
var c = cnStr.toCharArray()
|
||
for (i in 0..10) {
|
||
map[c[i]] = i
|
||
}
|
||
cnStr = "〇壹贰叁肆伍陆柒捌玖拾"
|
||
c = cnStr.toCharArray()
|
||
for (i in 0..10) {
|
||
map[c[i]] = i
|
||
}
|
||
map['两'] = 2
|
||
map['百'] = 100
|
||
map['佰'] = 100
|
||
map['千'] = 1000
|
||
map['仟'] = 1000
|
||
map['万'] = 10000
|
||
map['亿'] = 100000000
|
||
return map
|
||
}
|
||
|
||
/**
|
||
* 将日期转换成昨天、今天、明天
|
||
*/
|
||
fun dateConvert(source: String, pattern: String): String {
|
||
val format = SimpleDateFormat(pattern, Locale.getDefault())
|
||
val calendar = Calendar.getInstance()
|
||
kotlin.runCatching {
|
||
val date = format.parse(source) ?: return ""
|
||
val curTime = calendar.timeInMillis
|
||
calendar.time = date
|
||
//将MISC 转换成 sec
|
||
val difSec = abs((curTime - date.time) / 1000)
|
||
val difMin = difSec / 60
|
||
val difHour = difMin / 60
|
||
val difDate = difHour / 60
|
||
val oldHour = calendar.get(Calendar.HOUR)
|
||
//如果没有时间
|
||
if (oldHour == 0) {
|
||
//比日期:昨天今天和明天
|
||
return when {
|
||
difDate == 0L -> "今天"
|
||
difDate < DAY_OF_YESTERDAY -> "昨天"
|
||
else -> {
|
||
@SuppressLint("SimpleDateFormat")
|
||
val convertFormat = SimpleDateFormat("yyyy-MM-dd")
|
||
convertFormat.format(date)
|
||
}
|
||
}
|
||
}
|
||
|
||
return when {
|
||
difSec < TIME_UNIT -> difSec.toString() + "秒前"
|
||
difMin < TIME_UNIT -> difMin.toString() + "分钟前"
|
||
difHour < HOUR_OF_DAY -> difHour.toString() + "小时前"
|
||
difDate < DAY_OF_YESTERDAY -> "昨天"
|
||
else -> {
|
||
@SuppressLint("SimpleDateFormat")
|
||
val convertFormat = SimpleDateFormat("yyyy-MM-dd")
|
||
convertFormat.format(date)
|
||
}
|
||
}
|
||
}.onFailure {
|
||
it.printOnDebug()
|
||
}
|
||
return ""
|
||
}
|
||
|
||
/**
|
||
* 首字母大写
|
||
*/
|
||
@SuppressLint("DefaultLocale")
|
||
fun toFirstCapital(str: String): String {
|
||
return str.substring(0, 1).uppercase(Locale.getDefault()) + str.substring(1)
|
||
}
|
||
|
||
/**
|
||
* 将文本中的半角字符,转换成全角字符
|
||
*/
|
||
fun halfToFull(input: String): String {
|
||
val c = input.toCharArray()
|
||
for (i in c.indices) {
|
||
if (c[i].code == 32)
|
||
//半角空格
|
||
{
|
||
c[i] = 12288.toChar()
|
||
continue
|
||
}
|
||
//根据实际情况,过滤不需要转换的符号
|
||
//if (c[i] == 46) //半角点号,不转换
|
||
// continue;
|
||
|
||
if (c[i].code in 33..126)
|
||
//其他符号都转换为全角
|
||
c[i] = (c[i].code + 65248).toChar()
|
||
}
|
||
return String(c)
|
||
}
|
||
|
||
/**
|
||
* 字符串全角转换为半角
|
||
*/
|
||
fun fullToHalf(input: String): String {
|
||
val c = input.toCharArray()
|
||
for (i in c.indices) {
|
||
if (c[i].code == 12288)
|
||
//全角空格
|
||
{
|
||
c[i] = 32.toChar()
|
||
continue
|
||
}
|
||
|
||
if (c[i].code in 65281..65374)
|
||
c[i] = (c[i].code - 65248).toChar()
|
||
}
|
||
return String(c)
|
||
}
|
||
|
||
/**
|
||
* 中文大写数字转数字
|
||
*/
|
||
fun chineseNumToInt(chNum: String): Int {
|
||
var result = 0
|
||
var tmp = 0
|
||
var billion = 0
|
||
val cn = chNum.toCharArray()
|
||
|
||
// "一零二五" 形式
|
||
if (cn.size > 1 && chNum.matches("^[〇零一二三四五六七八九壹贰叁肆伍陆柒捌玖]$".toRegex())) {
|
||
for (i in cn.indices) {
|
||
cn[i] = (48 + ChnMap[cn[i]]!!).toChar()
|
||
}
|
||
return Integer.parseInt(String(cn))
|
||
}
|
||
|
||
// "一千零二十五", "一千二" 形式
|
||
return kotlin.runCatching {
|
||
for (i in cn.indices) {
|
||
val tmpNum = ChnMap[cn[i]]!!
|
||
when {
|
||
tmpNum == 100000000 -> {
|
||
result += tmp
|
||
result *= tmpNum
|
||
billion = billion * 100000000 + result
|
||
result = 0
|
||
tmp = 0
|
||
}
|
||
|
||
tmpNum == 10000 -> {
|
||
result += tmp
|
||
result *= tmpNum
|
||
tmp = 0
|
||
}
|
||
|
||
tmpNum >= 10 -> {
|
||
if (tmp == 0)
|
||
tmp = 1
|
||
result += tmpNum * tmp
|
||
tmp = 0
|
||
}
|
||
|
||
else -> {
|
||
tmp = if (i >= 2 && i == cn.size - 1 && ChnMap[cn[i - 1]]!! > 10)
|
||
tmpNum * ChnMap[cn[i - 1]]!! / 10
|
||
else
|
||
tmp * 10 + tmpNum
|
||
}
|
||
}
|
||
}
|
||
result += tmp + billion
|
||
result
|
||
}.getOrDefault(-1)
|
||
}
|
||
|
||
/**
|
||
* 字符串转数字
|
||
*/
|
||
fun stringToInt(str: String?): Int {
|
||
if (str != null) {
|
||
val num = fullToHalf(str).replace("\\s+".toRegex(), "")
|
||
return kotlin.runCatching {
|
||
Integer.parseInt(num)
|
||
}.getOrElse {
|
||
chineseNumToInt(num)
|
||
}
|
||
}
|
||
return -1
|
||
}
|
||
|
||
/**
|
||
* 是否包含数字
|
||
*/
|
||
fun isContainNumber(company: String): Boolean {
|
||
val p = Pattern.compile("[0-9]+")
|
||
val m = p.matcher(company)
|
||
return m.find()
|
||
}
|
||
|
||
/**
|
||
* 是否数字
|
||
*/
|
||
fun isNumeric(str: String): Boolean {
|
||
val pattern = Pattern.compile("-?[0-9]+")
|
||
val isNum = pattern.matcher(str)
|
||
return isNum.matches()
|
||
}
|
||
|
||
fun wordCountFormat(words: Int): String {
|
||
var wordsS = ""
|
||
if (words > 0) {
|
||
if (words > 10000) {
|
||
val df = wordCountFormatter
|
||
wordsS = df.format(words * 1.0f / 10000f.toDouble()) + "万字"
|
||
} else {
|
||
wordsS = words.toString() + "字"
|
||
}
|
||
}
|
||
return wordsS
|
||
}
|
||
|
||
fun wordCountFormat(wc: String?): String {
|
||
if (wc == null) return ""
|
||
var wordsS = ""
|
||
if (isNumeric(wc)) {
|
||
val words: Int = wc.toInt()
|
||
if (words > 0) {
|
||
if (words > 10000) {
|
||
val df = wordCountFormatter
|
||
wordsS = df.format(words * 1.0f / 10000f.toDouble()) + "万字"
|
||
} else {
|
||
wordsS = words.toString() + "字"
|
||
}
|
||
}
|
||
} else {
|
||
wordsS = wc
|
||
}
|
||
return wordsS
|
||
}
|
||
|
||
/**
|
||
* 移除字符串首尾空字符的高效方法(利用ASCII值判断,包括全角空格)
|
||
*/
|
||
fun trim(s: String): String {
|
||
if (isEmpty(s)) return ""
|
||
var start = 0
|
||
val len = s.length
|
||
var end = len - 1
|
||
while (start < end && (s[start].code <= 0x20 || s[start] == ' ')) {
|
||
++start
|
||
}
|
||
while (start < end && (s[end].code <= 0x20 || s[end] == ' ')) {
|
||
--end
|
||
}
|
||
++end
|
||
return if (start > 0 || end < len) s.substring(start, end) else s
|
||
}
|
||
|
||
/**
|
||
* 重复字符串
|
||
*/
|
||
fun repeat(str: String, n: Int): String {
|
||
val stringBuilder = StringBuilder()
|
||
for (i in 0 until n) {
|
||
stringBuilder.append(str)
|
||
}
|
||
return stringBuilder.toString()
|
||
}
|
||
|
||
/**
|
||
* 移除UTF头
|
||
*/
|
||
fun removeUTFCharacters(data: String?): String? {
|
||
if (data == null) return null
|
||
val p = Pattern.compile("\\\\u(\\p{XDigit}{4})")
|
||
val m = p.matcher(data)
|
||
val buf = StringBuffer(data.length)
|
||
while (m.find()) {
|
||
val ch = Integer.parseInt(m.group(1)!!, 16).toChar().toString()
|
||
m.appendReplacement(buf, Matcher.quoteReplacement(ch))
|
||
}
|
||
m.appendTail(buf)
|
||
return buf.toString()
|
||
}
|
||
|
||
/**
|
||
* 压缩字符串
|
||
*/
|
||
fun compress(str: String): Result<String> {
|
||
return kotlin.runCatching {
|
||
if (str.isEmpty()) {
|
||
return@runCatching str
|
||
}
|
||
val out = ByteArrayOutputStream()
|
||
var gzip: GZIPOutputStream? = null
|
||
return@runCatching try {
|
||
gzip = GZIPOutputStream(out)
|
||
gzip.write(str.toByteArray())
|
||
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
|
||
} finally {
|
||
gzip?.runCatching {
|
||
close()
|
||
}
|
||
out.runCatching {
|
||
close()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解压字符串
|
||
*/
|
||
@Throws(IOException::class)
|
||
fun unCompress(str: String): Result<String> {
|
||
return kotlin.runCatching {
|
||
val outputStream = ByteArrayOutputStream()
|
||
var inputStream: ByteArrayInputStream? = null
|
||
var ginZip: GZIPInputStream? = null
|
||
return@runCatching try {
|
||
val compressed = Base64.decode(str, Base64.NO_WRAP)
|
||
inputStream = ByteArrayInputStream(compressed)
|
||
ginZip = GZIPInputStream(inputStream)
|
||
ginZip.copyTo(outputStream)
|
||
outputStream.toString()
|
||
} finally {
|
||
ginZip?.runCatching {
|
||
close()
|
||
}
|
||
inputStream?.runCatching {
|
||
close()
|
||
}
|
||
outputStream.runCatching {
|
||
close()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|