├── .gitattributes ├── .gitignore ├── README.md ├── pom.xml ├── resource └── TimeExp.m ├── src └── com │ └── time │ ├── enums │ └── RangeTimeEnum.java │ ├── nlp │ ├── TimeNormalizer.java │ ├── TimePoint.java │ ├── TimeUnit.java │ └── stringPreHandlingModule.java │ └── util │ ├── CommonDateUtil.java │ ├── DateUtil.java │ └── StringUtil.java └── test └── com └── shinyke └── TimeAnalyseTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.classpath 2 | .project 3 | .settings 4 | *.prefs 5 | .svn/entries 6 | .svn/format 7 | *.svn-base 8 | .svn/wc.db 9 | target 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Time-NLP 2 | #中文语句中的时间语义识别 3 | 4 | author:shinyke 5 | 6 | 7 | 本工具是由复旦NLP中的时间分析功能修改而来,做了很多细节和功能的优化,具体如下: 8 | 9 | 1. **泛指时间**的支持,如:早上、晚上、中午、傍晚等。 10 | 2. 时间**未来倾向**。 如:在周五输入“周一早上开会”,则识别到下周一早上的时间;在下午17点输入:“9点送牛奶给隔壁的汉子”则识别到第二天上午9点。 11 | 3. **多个时间的识别,及多个时间之间上下文关系处理**。如:"下月1号下午3点至5点到图书馆还书",识别到开始时间为下月1号下午三点。同时,结束时间也**继承上文时间**,识别到下月1号下午5点。 12 | 4. 可**自定义基准时间**:指定基准时间为“2016-05-20-09-00-00-00”,则一切分析以此时间为基准。 13 | 5. **修复了各种各样的BUG**。 14 | 15 | 简而言之,这是一个输入一句话,能识别出话里的时间的工具。╮(╯▽╰)╭ 16 | 17 | 使用方法详见测试类: 18 | ``` java 19 | /** 20 | *

21 | * 测试类 22 | *

23 | * @author kexm 24 | * @version 1.0 25 | * @since 2016年5月4日 26 | * 27 | */ 28 | public class TimeAnalyseTest { 29 | 30 | @Test 31 | public void test(){ 32 | String path = TimeNormalizer.class.getResource("").getPath(); 33 | String classPath = path.substring(0, path.indexOf("/com/time")); 34 | System.out.println(classPath+"/TimeExp.m"); 35 | TimeNormalizer normalizer = new TimeNormalizer(classPath+"/TimeExp.m"); 36 | 37 | 38 | normalizer.parse("Hi,all.下周一下午三点开会");// 抽取时间 39 | TimeUnit[] unit = normalizer.getTimeUnit(); 40 | System.out.println("Hi,all.下周一下午三点开会"); 41 | System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 42 | 43 | normalizer.parse("早上六点起床");// 注意此处识别到6天在今天已经过去,自动识别为明早六点(未来倾向,可通过开关关闭:new TimeNormalizer(classPath+"/TimeExp.m", false)) 44 | unit = normalizer.getTimeUnit(); 45 | System.out.println("早上六点起床"); 46 | System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 47 | 48 | normalizer.parse("周一开会");// 如果本周已经是周二,识别为下周周一。同理处理各级时间。(未来倾向) 49 | unit = normalizer.getTimeUnit(); 50 | System.out.println("周一开会"); 51 | System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 52 | 53 | normalizer.parse("下下周一开会");//对于上/下的识别 54 | unit = normalizer.getTimeUnit(); 55 | System.out.println("下下周一开会"); 56 | System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 57 | 58 | normalizer.parse("6:30 起床");// 严格时间格式的识别 59 | unit = normalizer.getTimeUnit(); 60 | System.out.println("6:30 起床"); 61 | System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 62 | 63 | normalizer.parse("6-3 春游");// 严格时间格式的识别 64 | unit = normalizer.getTimeUnit(); 65 | System.out.println("6-3 春游"); 66 | System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 67 | 68 | normalizer.parse("6月3 春游");// 残缺时间的识别 (打字输入时可便捷用户) 69 | unit = normalizer.getTimeUnit(); 70 | System.out.println("6月3 春游"); 71 | System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 72 | 73 | normalizer.parse("明天早上跑步");// 模糊时间范围识别(可在RangeTimeEnum中修改 74 | unit = normalizer.getTimeUnit(); 75 | System.out.println("明天早上跑步"); 76 | System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 77 | 78 | normalizer.parse("本周日到下周日出差");// 多时间识别 79 | unit = normalizer.getTimeUnit(); 80 | System.out.println("本周日到下周日出差"); 81 | System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 82 | System.out.println(DateUtil.formatDateDefault(unit[1].getTime()) + "-" + unit[1].getIsAllDayTime()); 83 | 84 | normalizer.parse("周四下午三点到五点开会");// 多时间识别,注意第二个时间点用了第一个时间的上文 85 | unit = normalizer.getTimeUnit(); 86 | System.out.println("周四下午三点到五点开会"); 87 | System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 88 | System.out.println(DateUtil.formatDateDefault(unit[1].getTime()) + "-" + unit[1].getIsAllDayTime()); 89 | 90 | //新闻随机抽取长句识别(2016年6月7日新闻,均以当日0点为基准时间计算) 91 | //例1 92 | normalizer.parse("昨天上午,第八轮中美战略与经济对话气候变化问题特别联合会议召开。中国气候变化事务特别代表解振华表示,今年中美两国在应对气候变化多边进程中政策对话的重点任务,是推动《巴黎协定》尽早生效。", "2016-06-07-00-00-00"); 93 | unit = normalizer.getTimeUnit(); 94 | System.out.println("昨天上午,第八轮中美战略与经济对话气候变化问题特别联合会议召开。中国气候变化事务特别代表解振华表示,今年中美两国在应对气候变化多边进程中政策对话的重点任务,是推动《巴黎协定》尽早生效。"); 95 | for(int i = 0; i < unit.length; i++){ 96 | System.out.println("时间文本:"+unit[i].Time_Expression +",对应时间:"+ DateUtil.formatDateDefault(unit[i].getTime())); 97 | } 98 | 99 | //例2 100 | normalizer.parse("《辽宁日报》今日报道,6月3日辽宁召开省委常委扩大会,会议从下午两点半开到六点半,主要议题为:落实中央巡视整改要求。", "2016-06-07-00-00-00"); 101 | unit = normalizer.getTimeUnit(); 102 | System.out.println("《辽宁日报》今日报道,6月3日辽宁召开省委常委扩大会,会议从下午两点半开到六点半,主要议题为:落实中央巡视整改要求。"); 103 | for(int i = 0; i < unit.length; i++){ 104 | System.out.println("时间文本:"+unit[i].Time_Expression +",对应时间:"+ DateUtil.formatDateDefault(unit[i].getTime())); 105 | } 106 | 107 | //例3 108 | normalizer.parse("去年11月起正式实施的刑法修正案(九)中明确,在法律规定的国家考试中,组织作弊的将入刑定罪,最高可处七年有期徒刑。另外,本月刚刚开始实施的新版《教育法》中也明确...", "2016-06-07-00-00-00"); 109 | unit = normalizer.getTimeUnit(); 110 | System.out.println("去年11月起正式实施的刑法修正案(九)中明确,在法律规定的国家考试中,组织作弊的将入刑定罪,最高可处七年有期徒刑。另外,本月刚刚开始实施的新版《教育法》中也明确..."); 111 | for(int i = 0; i < unit.length; i++){ 112 | System.out.println("时间文本:"+unit[i].Time_Expression +",对应时间:"+ DateUtil.formatDateDefault(unit[i].getTime())); 113 | } 114 | } 115 | 116 | /** 117 | * 修改TimeExp.m文件的内容 118 | */ 119 | @Test 120 | public void editTimeExp(){ 121 | String path = TimeNormalizer.class.getResource("").getPath(); 122 | String classPath = path.substring(0, path.indexOf("/com/time")); 123 | System.out.println(classPath+"/TimeExp.m"); 124 | /**写TimeExp*/ 125 | Pattern p = Pattern.compile("your-regex"); 126 | try { 127 | TimeNormalizer.writeModel(p, classPath+"/TimeExp.m"); 128 | } catch (Exception e) { 129 | e.printStackTrace(); 130 | } 131 | } 132 | } 133 | ``` 134 |
135 | 136 | 在`2016年6月7日9:44`执行測試,结果如下: 137 | 138 | 139 | Hi,all。下周一下午三点开会 140 | 141 | 2016-06-13 15:00:00-false 142 | 143 | 周一开会 144 | 145 | 2016-06-13 00:00:00-true 146 | 147 | 下下周一开会 148 | 149 | 2016-06-20 00:00:00-true 150 | 151 | 6:30 起床 152 | 153 | 2016-06-08 06:30:00-false 154 | 155 | 6-3 春游 156 | 157 | 2016-06-03 00:00:00-true 158 | 159 | 6月3日 春游 160 | 161 | 2016-06-03 00:00:00-true 162 | 163 | 明天早上跑步 164 | 165 | 2016-06-08 08:00:00-false 166 | 167 | 本周日到下周日出差 168 | 169 | 2016-06-12 00:00:00-true 170 | 171 | 2016-06-19 00:00:00-true 172 | 173 | 周四下午三点到五点开会 174 | 175 | 2016-06-16 15:00:00-false 176 | 177 | 2016-06-16 17:00:00-false 178 | 179 | 昨天上午,第八轮中美战略与经济对话气候变化问题特别联合会议召开。中国气候变化事务特别代表解振华表示,今年中美两国在应对气候变化多边进程中政策对话的重点任务,是推动《巴黎协定》尽早生效。 180 | 181 | 时间文本:昨天上午, 对应时间:2016-06-06 10:00:00 182 | 183 | 时间文本:今年, 对应时间:2016-01-01 00:00:00 184 | 185 | 《辽宁日报》今日报道,6月3日辽宁召开省委常委扩大会,会议从下午两点半开到六点半,主要议题为:落实中央巡视整改要求。 186 | 187 | 时间文本:今日, 对应时间:2016-06-07 00:00:00 188 | 189 | 时间文本:6月3日, 对应时间:2016-06-03 00:00:00 190 | 191 | 时间文本:下午2点半, 对应时间:2016-06-03 14:30:00 192 | 193 | 时间文本:6点半, 对应时间:2016-06-03 18:30:00 194 | 195 | 196 | 去年11月起正式实施的刑法修正案(九)中明确,在法律规定的国家考试中,组织作弊的将入刑定罪,最高可处七年有期徒刑。另外,本月刚刚开始实施的新版《教育法》中也明确... 197 | 198 | 时间文本:去年11月, 对应时间:2015-11-01 00:00:00 199 | 200 | 时间文本:本月, 对应时间:2016-06-01 00:00:00 201 | 202 | 203 | 204 |
205 |
206 | 如果您使用并有意见和建议,欢迎在Issue和我交流。若觉得好用,你的star是对作者最好的支持。
207 | 208 | Enjoy ** - shinyke** 209 | 210 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.cn21 5 | Time-NLP 6 | 1.0.0 7 | Time-NLP 8 | 9 | 10 | src 11 | 12 | 13 | resource 14 | 15 | **/* 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | org.apache.maven.plugins 32 | maven-source-plugin 33 | 34 | 35 | attach-sources 36 | 37 | jar 38 | 39 | 40 | 41 | 42 | 43 | maven-compiler-plugin 44 | 3.3 45 | 46 | 1.7 47 | 1.7 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | OSChina Central 56 | http://maven.oschina.net/content/groups/public/ 57 | 58 | 59 | 60 | 61 | 62 | commons-cli 63 | commons-cli 64 | 1.2 65 | 66 | 67 | 68 | junit 69 | junit 70 | 4.11 71 | test 72 | 73 | 74 | org.slf4j 75 | slf4j-api 76 | 1.7.23 77 | 78 | 79 | -------------------------------------------------------------------------------- /resource/TimeExp.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinyke/Time-NLP/e8dabd5dfe12584ba305a82f0e57871eb7d0b34e/resource/TimeExp.m -------------------------------------------------------------------------------- /src/com/time/enums/RangeTimeEnum.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 21CN.COM . All rights reserved.
3 | * 4 | * Description: fudannlp
5 | * 6 | * Modified log:
7 | * ------------------------------------------------------
8 | * Ver. Date Author Description
9 | * ------------------------------------------------------
10 | * 1.0 2016年5月3日 kexm created.
11 | */ 12 | package com.time.enums; 13 | 14 | /** 15 | *

16 | * 范围时间的默认时间点 17 | *

18 | * @author kexm 19 | * @version 20 | * @since 2016年5月3日 21 | * 22 | */ 23 | public enum RangeTimeEnum { 24 | 25 | day_break(3), 26 | early_morning(8), //早 27 | morning(10), //上午 28 | noon(12), //中午、午间 29 | afternoon(15), //下午、午后 30 | night(18), //晚上、傍晚 31 | lateNight(20), //晚、晚间 32 | midNight(23); //深夜 33 | 34 | private int hourTime = 0; 35 | 36 | /** 37 | * @param hourTime 38 | */ 39 | private RangeTimeEnum(int hourTime) { 40 | this.setHourTime(hourTime); 41 | } 42 | 43 | /** 44 | * @return the hourTime 45 | */ 46 | public int getHourTime() { 47 | return hourTime; 48 | } 49 | 50 | /** 51 | * @param hourTime the hourTime to set 52 | */ 53 | public void setHourTime(int hourTime) { 54 | this.hourTime = hourTime; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/com/time/nlp/TimeNormalizer.java: -------------------------------------------------------------------------------- 1 | package com.time.nlp; 2 | 3 | import com.time.util.DateUtil; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.BufferedInputStream; 8 | import java.io.BufferedOutputStream; 9 | import java.io.FileInputStream; 10 | import java.io.FileOutputStream; 11 | import java.io.InputStream; 12 | import java.io.ObjectInputStream; 13 | import java.io.ObjectOutputStream; 14 | import java.io.Serializable; 15 | import java.net.URL; 16 | import java.text.SimpleDateFormat; 17 | import java.util.ArrayList; 18 | import java.util.Calendar; 19 | import java.util.List; 20 | import java.util.regex.Matcher; 21 | import java.util.regex.Pattern; 22 | import java.util.zip.GZIPInputStream; 23 | import java.util.zip.GZIPOutputStream; 24 | 25 | /** 26 | *

27 | * 新版时间表达式识别的主要工作类 28 | *

29 | * 30 | * @author kexm 31 | * @since 2016年5月4日 32 | */ 33 | public class TimeNormalizer implements Serializable { 34 | 35 | private static final long serialVersionUID = 463541045644656392L; 36 | private static final Logger LOGGER = LoggerFactory.getLogger(TimeNormalizer.class); 37 | 38 | private String timeBase; 39 | private String oldTimeBase; 40 | private static Pattern patterns = null; 41 | private String target; 42 | private TimeUnit[] timeToken = new TimeUnit[0]; 43 | 44 | private boolean isPreferFuture = true; 45 | 46 | public TimeNormalizer() { 47 | if (patterns == null) { 48 | try { 49 | InputStream in = getClass().getResourceAsStream("/TimeExp.m"); 50 | ObjectInputStream objectInputStream = new ObjectInputStream( 51 | new BufferedInputStream(new GZIPInputStream(in))); 52 | patterns = readModel(objectInputStream); 53 | } catch (Exception e) { 54 | e.printStackTrace(); 55 | System.err.print("Read model error!"); 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * 参数为TimeExp.m文件路径 62 | * 63 | * @param path 64 | */ 65 | public TimeNormalizer(String path) { 66 | if (patterns == null) { 67 | try { 68 | patterns = readModel(path); 69 | } catch (Exception e) { 70 | e.printStackTrace(); 71 | System.err.print("Read model error!"); 72 | } 73 | } 74 | } 75 | 76 | /** 77 | * 参数为TimeExp.m文件路径 78 | * 79 | * @param path 80 | */ 81 | public TimeNormalizer(String path, boolean isPreferFuture) { 82 | this.isPreferFuture = isPreferFuture; 83 | if (patterns == null) { 84 | try { 85 | patterns = readModel(path); 86 | LOGGER.debug("loaded pattern:{}", patterns.pattern()); 87 | } catch (Exception e) { 88 | // TODO Auto-generated catch block 89 | e.printStackTrace(); 90 | System.err.print("Read model error!"); 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * TimeNormalizer的构造方法,根据提供的待分析字符串和timeBase进行时间表达式提取 97 | * 在构造方法中已完成对待分析字符串的表达式提取工作 98 | * 99 | * @param target 待分析字符串 100 | * @param timeBase 给定的timeBase 101 | * @return 返回值 102 | */ 103 | public TimeUnit[] parse(String target, String timeBase) { 104 | this.target = target; 105 | this.timeBase = timeBase; 106 | this.oldTimeBase = timeBase; 107 | // 字符串预处理 108 | preHandling(); 109 | timeToken = TimeEx(this.target, timeBase); 110 | return timeToken; 111 | } 112 | 113 | /** 114 | * 同上的TimeNormalizer的构造方法,timeBase取默认的系统当前时间 115 | * 116 | * @param target 待分析字符串 117 | * @return 时间单元数组 118 | */ 119 | public TimeUnit[] parse(String target) { 120 | this.target = target; 121 | this.timeBase = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(Calendar.getInstance().getTime());// TODO 122 | // Calendar.getInstance().getTime()换成new 123 | // Date? 124 | this.oldTimeBase = timeBase; 125 | preHandling();// 字符串预处理 126 | timeToken = TimeEx(this.target, timeBase); 127 | return timeToken; 128 | } 129 | 130 | // 131 | 132 | /** 133 | * timeBase的get方法 134 | * 135 | * @return 返回值 136 | */ 137 | public String getTimeBase() { 138 | return timeBase; 139 | } 140 | 141 | /** 142 | * oldTimeBase的get方法 143 | * 144 | * @return 返回值 145 | */ 146 | public String getOldTimeBase() { 147 | return oldTimeBase; 148 | } 149 | 150 | public boolean isPreferFuture() { 151 | return isPreferFuture; 152 | } 153 | 154 | public void setPreferFuture(boolean isPreferFuture) { 155 | this.isPreferFuture = isPreferFuture; 156 | } 157 | 158 | /** 159 | * timeBase的set方法 160 | * 161 | * @param s timeBase 162 | */ 163 | public void setTimeBase(String s) { 164 | timeBase = s; 165 | } 166 | 167 | /** 168 | * 重置timeBase为oldTimeBase 169 | */ 170 | public void resetTimeBase() { 171 | timeBase = oldTimeBase; 172 | } 173 | 174 | /** 175 | * 时间分析结果以TimeUnit组的形式出现,此方法为分析结果的get方法 176 | * 177 | * @return 返回值 178 | */ 179 | public TimeUnit[] getTimeUnit() { 180 | return timeToken; 181 | } 182 | 183 | /** 184 | * 待匹配字符串的清理空白符和语气助词以及大写数字转化的预处理 185 | */ 186 | private void preHandling() { 187 | target = stringPreHandlingModule.delKeyword(target, "\\s+"); // 清理空白符 188 | target = stringPreHandlingModule.delKeyword(target, "[的]+"); // 清理语气助词 189 | target = stringPreHandlingModule.numberTranslator(target);// 大写数字转化 190 | // TODO 处理大小写标点符号 191 | } 192 | 193 | /** 194 | * 有基准时间输入的时间表达式识别 195 | *

196 | * 这是时间表达式识别的主方法, 通过已经构建的正则表达式对字符串进行识别,并按照预先定义的基准时间进行规范化 197 | * 将所有别识别并进行规范化的时间表达式进行返回, 时间表达式通过TimeUnit类进行定义 198 | * 199 | * @param String 输入文本字符串 200 | * @param String 输入基准时间 201 | * @return TimeUnit[] 时间表达式类型数组 202 | */ 203 | private TimeUnit[] TimeEx(String tar, String timebase) { 204 | Matcher match; 205 | int startline = -1, endline = -1; 206 | 207 | String[] temp = new String[99]; 208 | int rpointer = 0;// 计数器,记录当前识别到哪一个字符串了 209 | TimeUnit[] Time_Result = null; 210 | 211 | match = patterns.matcher(tar); 212 | boolean startmark = true; 213 | while (match.find()) { 214 | startline = match.start(); 215 | if (endline == startline) // 假如下一个识别到的时间字段和上一个是相连的 @author kexm 216 | { 217 | rpointer--; 218 | temp[rpointer] = temp[rpointer] + match.group();// 则把下一个识别到的时间字段加到上一个时间字段去 219 | } else { 220 | if (!startmark) { 221 | rpointer--; 222 | rpointer++; 223 | } 224 | startmark = false; 225 | temp[rpointer] = match.group();// 记录当前识别到的时间字段,并把startmark开关关闭。这个开关貌似没用? 226 | } 227 | endline = match.end(); 228 | rpointer++; 229 | } 230 | if (rpointer > 0) { 231 | rpointer--; 232 | rpointer++; 233 | } 234 | Time_Result = new TimeUnit[rpointer]; 235 | /**时间上下文: 前一个识别出来的时间会是下一个时间的上下文,用于处理:周六3点到5点这样的多个时间的识别,第二个5点应识别到是周六的。*/ 236 | TimePoint contextTp = new TimePoint(); 237 | for (int j = 0; j < rpointer; j++) { 238 | Time_Result[j] = new TimeUnit(temp[j], this, contextTp); 239 | contextTp = Time_Result[j]._tp; 240 | } 241 | /**过滤无法识别的字段*/ 242 | Time_Result = filterTimeUnit(Time_Result); 243 | return Time_Result; 244 | } 245 | 246 | /** 247 | * 过滤timeUnit中无用的识别词。无用识别词识别出的时间是1970.01.01 00:00:00(fastTime=-28800000) 248 | * 249 | * @param timeUnit 250 | * @return 251 | */ 252 | public static TimeUnit[] filterTimeUnit(TimeUnit[] timeUnit) { 253 | if (timeUnit == null || timeUnit.length < 1) { 254 | return timeUnit; 255 | } 256 | List list = new ArrayList<>(); 257 | for (TimeUnit t : timeUnit) { 258 | if (t.getTime().getTime() != -28800000) { 259 | list.add(t); 260 | } 261 | } 262 | TimeUnit[] newT = new TimeUnit[list.size()]; 263 | newT = list.toArray(newT); 264 | return newT; 265 | } 266 | 267 | private Pattern readModel(String file) throws Exception { 268 | ObjectInputStream in; 269 | if (file.startsWith("jar:file") || file.startsWith("file:")) { 270 | in = new ObjectInputStream(new BufferedInputStream(new GZIPInputStream(new URL(file).openStream()))); 271 | } else { 272 | in = new ObjectInputStream( 273 | new BufferedInputStream(new GZIPInputStream(new FileInputStream(file)))); 274 | } 275 | return readModel(in); 276 | } 277 | 278 | private Pattern readModel(ObjectInputStream in) throws Exception { 279 | Pattern p = (Pattern) in.readObject(); 280 | LOGGER.debug("model pattern:{}", p.pattern()); 281 | return Pattern.compile(p.pattern()); 282 | } 283 | 284 | public static void writeModel(Object p, String path) throws Exception { 285 | ObjectOutputStream out = new ObjectOutputStream( 286 | new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(path)))); 287 | out.writeObject(p); 288 | out.close(); 289 | } 290 | 291 | public static void main(String args[]) throws Exception { 292 | String path = TimeNormalizer.class.getResource("").getPath(); 293 | String classPath = path.substring(0, path.indexOf("/edu/fudan")); 294 | LOGGER.debug(classPath + "/TimeExp1.zip"); 295 | TimeNormalizer normalizer = new TimeNormalizer(classPath + "/TimeExp1.zip"); 296 | 297 | normalizer.parse("本周日到下周日出差");// 抽取时间 298 | TimeUnit[] unit = normalizer.getTimeUnit(); 299 | LOGGER.debug(DateUtil.formatDateDefault(unit[0].getTime())); 300 | LOGGER.debug(DateUtil.formatDateDefault(unit[1].getTime())); 301 | /**写TimeExp*/ 302 | // Pattern p = Pattern.compile("((前|昨|今|明|后)(天|日)?(早|晚)(晨|上|间)?)|(\\d+个?[年月日天][以之]?[前后])|(\\d+个?半?(小时|钟头|h|H))|(半个?(小时|钟头))|(\\d+(分钟|min))|([13]刻钟)|((上|这|本|下)+(周|星期)([一二三四五六七天日]|[1-7])?)|((周|星期)([一二三四五六七天日]|[1-7]))|((早|晚)?([0-2]?[0-9](点|时)半)(am|AM|pm|PM)?)|((早|晚)?(\\d+[::]\\d+([::]\\d+)*)\\s*(am|AM|pm|PM)?)|((早|晚)?([0-2]?[0-9](点|时)[13一三]刻)(am|AM|pm|PM)?)|((早|晚)?(\\d+[时点](\\d+)?分?(\\d+秒?)?)\\s*(am|AM|pm|PM)?)|(大+(前|后)天)|(([零一二三四五六七八九十百千万]+|\\d+)世)|([0-9]?[0-9]?[0-9]{2}\\.((10)|(11)|(12)|([1-9]))\\.((? 3 | * 4 | * Description: fudannlp
5 | * 6 | * Modified log:
7 | * ------------------------------------------------------
8 | * Ver. Date Author Description
9 | * ------------------------------------------------------
10 | * 1.0 2016年5月4日 kexm created.
11 | */ 12 | package com.time.nlp; 13 | 14 | /** 15 | *

16 | * 时间表达式单元规范化的内部类 17 | * 18 | * 时间表达式单元规范化对应的内部类, 19 | * 对应时间表达式规范化的每个字段, 20 | * 六个字段分别是:年-月-日-时-分-秒, 21 | * 每个字段初始化为-1 22 | *

23 | * @author kexm 24 | * @version 25 | * @since 2016年5月4日 26 | * 27 | */ 28 | public class TimePoint 29 | { 30 | int [] tunit={-1,-1,-1,-1,-1,-1}; 31 | } 32 | -------------------------------------------------------------------------------- /src/com/time/nlp/TimeUnit.java: -------------------------------------------------------------------------------- 1 | package com.time.nlp; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Calendar; 5 | import java.util.Date; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import com.time.enums.RangeTimeEnum; 12 | 13 | /** 14 | *

15 | * 时间语句分析 16 | *

17 | * 18 | * @author kexm 19 | * @since 2016年5月4日 20 | */ 21 | public class TimeUnit { 22 | //有需要可使用 23 | //private static final Logger LOGGER = LoggerFactory.getLogger(TimeUnit.class); 24 | /** 25 | * 目标字符串 26 | */ 27 | public String Time_Expression = null; 28 | public String Time_Norm = ""; 29 | public int[] time_full; 30 | public int[] time_origin; 31 | private Date time; 32 | private Boolean isAllDayTime = true; 33 | private boolean isFirstTimeSolveContext = true; 34 | 35 | TimeNormalizer normalizer = null; 36 | public TimePoint _tp = new TimePoint(); 37 | public TimePoint _tp_origin = new TimePoint(); 38 | 39 | /** 40 | * 时间表达式单元构造方法 41 | * 该方法作为时间表达式单元的入口,将时间表达式字符串传入 42 | * 43 | * @param exp_time 时间表达式字符串 44 | * @param n 45 | */ 46 | 47 | public TimeUnit(String exp_time, TimeNormalizer n) { 48 | Time_Expression = exp_time; 49 | normalizer = n; 50 | Time_Normalization(); 51 | } 52 | 53 | /** 54 | * 时间表达式单元构造方法 55 | * 该方法作为时间表达式单元的入口,将时间表达式字符串传入 56 | * 57 | * @param exp_time 时间表达式字符串 58 | * @param n 59 | * @param contextTp 上下文时间 60 | */ 61 | 62 | public TimeUnit(String exp_time, TimeNormalizer n, TimePoint contextTp) { 63 | Time_Expression = exp_time; 64 | normalizer = n; 65 | _tp_origin = contextTp; 66 | Time_Normalization(); 67 | } 68 | 69 | /** 70 | * return the accurate time object 71 | */ 72 | public Date getTime() { 73 | return time; 74 | } 75 | 76 | /** 77 | * 年-规范化方法 78 | *

79 | * 该方法识别时间表达式单元的年字段 80 | */ 81 | public void norm_setyear() { 82 | /**假如只有两位数来表示年份*/ 83 | String rule = "[0-9]{2}(?=年)"; 84 | Pattern pattern = Pattern.compile(rule); 85 | Matcher match = pattern.matcher(Time_Expression); 86 | if (match.find()) { 87 | _tp.tunit[0] = Integer.parseInt(match.group()); 88 | if (_tp.tunit[0] >= 0 && _tp.tunit[0] < 100) { 89 | if (_tp.tunit[0] < 30) /**30以下表示2000年以后的年份*/ 90 | _tp.tunit[0] += 2000; 91 | else/**否则表示1900年以后的年份*/ 92 | _tp.tunit[0] += 1900; 93 | } 94 | 95 | } 96 | /**不仅局限于支持1XXX年和2XXX年的识别,可识别三位数和四位数表示的年份*/ 97 | rule = "[0-9]?[0-9]{3}(?=年)"; 98 | 99 | pattern = Pattern.compile(rule); 100 | match = pattern.matcher(Time_Expression); 101 | if (match.find())/**如果有3位数和4位数的年份,则覆盖原来2位数识别出的年份*/ { 102 | _tp.tunit[0] = Integer.parseInt(match.group()); 103 | } 104 | } 105 | 106 | /** 107 | * 月-规范化方法 108 | *

109 | * 该方法识别时间表达式单元的月字段 110 | */ 111 | public void norm_setmonth() { 112 | String rule = "((10)|(11)|(12)|([1-9]))(?=月)"; 113 | Pattern pattern = Pattern.compile(rule); 114 | Matcher match = pattern.matcher(Time_Expression); 115 | if (match.find()) { 116 | _tp.tunit[1] = Integer.parseInt(match.group()); 117 | 118 | /**处理倾向于未来时间的情况 @author kexm*/ 119 | preferFuture(1); 120 | } 121 | } 122 | 123 | /** 124 | * 月-日 兼容模糊写法 125 | *

126 | * 该方法识别时间表达式单元的月、日字段 127 | *

128 | * add by kexm 129 | */ 130 | public void norm_setmonth_fuzzyday() { 131 | String rule = "((10)|(11)|(12)|([1-9]))(月|\\.|\\-)([0-3][0-9]|[1-9])"; 132 | Pattern pattern = Pattern.compile(rule); 133 | Matcher match = pattern.matcher(Time_Expression); 134 | if (match.find()) { 135 | String matchStr = match.group(); 136 | Pattern p = Pattern.compile("(月|\\.|\\-)"); 137 | Matcher m = p.matcher(matchStr); 138 | if (m.find()) { 139 | int splitIndex = m.start(); 140 | String month = matchStr.substring(0, splitIndex); 141 | String date = matchStr.substring(splitIndex + 1); 142 | 143 | _tp.tunit[1] = Integer.parseInt(month); 144 | _tp.tunit[2] = Integer.parseInt(date); 145 | 146 | /**处理倾向于未来时间的情况 @author kexm*/ 147 | preferFuture(1); 148 | } 149 | } 150 | } 151 | 152 | /** 153 | * 日-规范化方法 154 | *

155 | * 该方法识别时间表达式单元的日字段 156 | */ 157 | public void norm_setday() { 158 | String rule = "((? 172 | * 该方法识别时间表达式单元的时字段 173 | */ 174 | public void norm_sethour() { 175 | String rule = "(?= 0 && _tp.tunit[3] <= 10) 233 | _tp.tunit[3] += 12; 234 | if (_tp.tunit[3] == -1) /**增加对没有明确时间点,只写了“中午/午间”这种情况的处理 @author kexm*/ 235 | _tp.tunit[3] = RangeTimeEnum.noon.getHourTime(); 236 | /**处理倾向于未来时间的情况 @author kexm*/ 237 | preferFuture(3); 238 | isAllDayTime = false; 239 | } 240 | 241 | rule = "(下午)|(午后)|(pm)|(PM)"; 242 | pattern = Pattern.compile(rule); 243 | match = pattern.matcher(Time_Expression); 244 | if (match.find()) { 245 | if (_tp.tunit[3] >= 0 && _tp.tunit[3] <= 11) 246 | _tp.tunit[3] += 12; 247 | if (_tp.tunit[3] == -1) /**增加对没有明确时间点,只写了“下午|午后”这种情况的处理 @author kexm*/ 248 | _tp.tunit[3] = RangeTimeEnum.afternoon.getHourTime(); 249 | /**处理倾向于未来时间的情况 @author kexm*/ 250 | preferFuture(3); 251 | isAllDayTime = false; 252 | } 253 | 254 | rule = "晚上|夜间|夜里|今晚|明晚"; 255 | pattern = Pattern.compile(rule); 256 | match = pattern.matcher(Time_Expression); 257 | if (match.find()) { 258 | if (_tp.tunit[3] >= 1 && _tp.tunit[3] <= 11) 259 | _tp.tunit[3] += 12; 260 | else if (_tp.tunit[3] == 12) 261 | _tp.tunit[3] = 0; 262 | else if (_tp.tunit[3] == -1) 263 | _tp.tunit[3] = RangeTimeEnum.night.getHourTime(); 264 | 265 | /**处理倾向于未来时间的情况 @author kexm*/ 266 | preferFuture(3); 267 | isAllDayTime = false; 268 | } 269 | 270 | } 271 | 272 | /** 273 | * 分-规范化方法 274 | *

275 | * 该方法识别时间表达式单元的分字段 276 | */ 277 | public void norm_setminute() { 278 | String rule = "([0-5]?[0-9](?=分(?!钟)))|((?<=((? 325 | * 该方法识别时间表达式单元的秒字段 326 | */ 327 | public void norm_setsecond() { 328 | /* 329 | * 添加了省略“分”说法的时间 330 | * 如17点15分32 331 | * modified by 曹零 332 | */ 333 | String rule = "([0-5]?[0-9](?=秒))|((?<=分)[0-5]?[0-9])"; 334 | 335 | Pattern pattern = Pattern.compile(rule); 336 | Matcher match = pattern.matcher(Time_Expression); 337 | if (match.find()) { 338 | _tp.tunit[5] = Integer.parseInt(match.group()); 339 | isAllDayTime = false; 340 | } 341 | } 342 | 343 | /** 344 | * 特殊形式的规范化方法 345 | *

346 | * 该方法识别特殊形式的时间表达式单元的各个字段 347 | */ 348 | public void norm_setTotal() { 349 | String rule; 350 | Pattern pattern; 351 | Matcher match; 352 | String[] tmp_parser; 353 | String tmp_target; 354 | 355 | rule = "(?= 0 && _tp.tunit[3] <= 10) 393 | _tp.tunit[3] += 12; 394 | if (_tp.tunit[3] == -1) /**增加对没有明确时间点,只写了“中午/午间”这种情况的处理 @author kexm*/ 395 | _tp.tunit[3] = RangeTimeEnum.noon.getHourTime(); 396 | /**处理倾向于未来时间的情况 @author kexm*/ 397 | preferFuture(3); 398 | isAllDayTime = false; 399 | 400 | } 401 | 402 | rule = "(下午)|(午后)|(pm)|(PM)"; 403 | pattern = Pattern.compile(rule); 404 | match = pattern.matcher(Time_Expression); 405 | if (match.find()) { 406 | if (_tp.tunit[3] >= 0 && _tp.tunit[3] <= 11) 407 | _tp.tunit[3] += 12; 408 | if (_tp.tunit[3] == -1) /**增加对没有明确时间点,只写了“中午/午间”这种情况的处理 @author kexm*/ 409 | _tp.tunit[3] = RangeTimeEnum.afternoon.getHourTime(); 410 | /**处理倾向于未来时间的情况 @author kexm*/ 411 | preferFuture(3); 412 | isAllDayTime = false; 413 | } 414 | 415 | rule = "晚"; 416 | pattern = Pattern.compile(rule); 417 | match = pattern.matcher(Time_Expression); 418 | if (match.find()) { 419 | if (_tp.tunit[3] >= 1 && _tp.tunit[3] <= 11) 420 | _tp.tunit[3] += 12; 421 | else if (_tp.tunit[3] == 12) 422 | _tp.tunit[3] = 0; 423 | if (_tp.tunit[3] == -1) /**增加对没有明确时间点,只写了“中午/午间”这种情况的处理 @author kexm*/ 424 | _tp.tunit[3] = RangeTimeEnum.night.getHourTime(); 425 | /**处理倾向于未来时间的情况 @author kexm*/ 426 | preferFuture(3); 427 | isAllDayTime = false; 428 | } 429 | 430 | 431 | rule = "[0-9]?[0-9]?[0-9]{2}-((10)|(11)|(12)|([1-9]))-((? 827 | * 时间表达式识别后,通过此入口进入规范化阶段, 828 | * 具体识别每个字段的值 829 | */ 830 | public void Time_Normalization() { 831 | norm_setyear(); 832 | norm_setmonth(); 833 | norm_setday(); 834 | norm_setmonth_fuzzyday();/**add by kexm*/ 835 | norm_setBaseRelated(); 836 | norm_setCurRelated(); 837 | norm_sethour(); 838 | norm_setminute(); 839 | norm_setsecond(); 840 | norm_setTotal(); 841 | modifyTimeBase(); 842 | 843 | _tp_origin.tunit = _tp.tunit.clone(); 844 | 845 | String[] time_grid = new String[6]; 846 | time_grid = normalizer.getTimeBase().split("-"); 847 | 848 | int tunitpointer = 5; 849 | while (tunitpointer >= 0 && _tp.tunit[tunitpointer] < 0) { 850 | tunitpointer--; 851 | } 852 | for (int i = 0; i < tunitpointer; i++) { 853 | if (_tp.tunit[i] < 0) 854 | _tp.tunit[i] = Integer.parseInt(time_grid[i]); 855 | } 856 | String[] _result_tmp = new String[6]; 857 | _result_tmp[0] = String.valueOf(_tp.tunit[0]); 858 | if (_tp.tunit[0] >= 10 && _tp.tunit[0] < 100) { 859 | _result_tmp[0] = "19" + String.valueOf(_tp.tunit[0]); 860 | } 861 | if (_tp.tunit[0] > 0 && _tp.tunit[0] < 10) { 862 | _result_tmp[0] = "200" + String.valueOf(_tp.tunit[0]); 863 | } 864 | 865 | for (int i = 1; i < 6; i++) { 866 | _result_tmp[i] = String.valueOf(_tp.tunit[i]); 867 | } 868 | 869 | Calendar cale = Calendar.getInstance(); //leverage a calendar object to figure out the final time 870 | cale.clear(); 871 | if (Integer.parseInt(_result_tmp[0]) != -1) { 872 | Time_Norm += _result_tmp[0] + "年"; 873 | cale.set(Calendar.YEAR, Integer.valueOf(_result_tmp[0])); 874 | if (Integer.parseInt(_result_tmp[1]) != -1) { 875 | Time_Norm += _result_tmp[1] + "月"; 876 | cale.set(Calendar.MONTH, Integer.valueOf(_result_tmp[1]) - 1); 877 | if (Integer.parseInt(_result_tmp[2]) != -1) { 878 | Time_Norm += _result_tmp[2] + "日"; 879 | cale.set(Calendar.DAY_OF_MONTH, Integer.valueOf(_result_tmp[2])); 880 | if (Integer.parseInt(_result_tmp[3]) != -1) { 881 | Time_Norm += _result_tmp[3] + "时"; 882 | cale.set(Calendar.HOUR_OF_DAY, Integer.valueOf(_result_tmp[3])); 883 | if (Integer.parseInt(_result_tmp[4]) != -1) { 884 | Time_Norm += _result_tmp[4] + "分"; 885 | cale.set(Calendar.MINUTE, Integer.valueOf(_result_tmp[4])); 886 | if (Integer.parseInt(_result_tmp[5]) != -1) { 887 | Time_Norm += _result_tmp[5] + "秒"; 888 | cale.set(Calendar.SECOND, Integer.valueOf(_result_tmp[5])); 889 | } 890 | } 891 | } 892 | } 893 | } 894 | } 895 | time = cale.getTime(); 896 | 897 | time_full = _tp.tunit.clone(); 898 | // time_origin = _tp_origin.tunit.clone(); comment by kexm 899 | } 900 | 901 | public Boolean getIsAllDayTime() { 902 | return isAllDayTime; 903 | } 904 | 905 | public void setIsAllDayTime(Boolean isAllDayTime) { 906 | this.isAllDayTime = isAllDayTime; 907 | } 908 | 909 | public String toString() { 910 | return Time_Expression + " ---> " + Time_Norm; 911 | } 912 | 913 | /** 914 | * 如果用户选项是倾向于未来时间,检查checkTimeIndex所指的时间是否是过去的时间,如果是的话,将大一级的时间设为当前时间的+1。 915 | *

916 | * 如在晚上说“早上8点看书”,则识别为明天早上; 917 | * 12月31日说“3号买菜”,则识别为明年1月的3号。 918 | * 919 | * @param checkTimeIndex _tp.tunit时间数组的下标 920 | */ 921 | private void preferFuture(int checkTimeIndex) { 922 | /**1. 检查被检查的时间级别之前,是否没有更高级的已经确定的时间,如果有,则不进行处理.*/ 923 | for (int i = 0; i < checkTimeIndex; i++) { 924 | if (_tp.tunit[i] != -1) return; 925 | } 926 | /**2. 根据上下文补充时间*/ 927 | checkContextTime(checkTimeIndex); 928 | /**3. 根据上下文补充时间后再次检查被检查的时间级别之前,是否没有更高级的已经确定的时间,如果有,则不进行倾向处理.*/ 929 | for (int i = 0; i < checkTimeIndex; i++) { 930 | if (_tp.tunit[i] != -1) return; 931 | } 932 | /**4. 确认用户选项*/ 933 | if (!normalizer.isPreferFuture()) { 934 | return; 935 | } 936 | /**5. 获取当前时间,如果识别到的时间小于当前时间,则将其上的所有级别时间设置为当前时间,并且其上一级的时间步长+1*/ 937 | Calendar c = Calendar.getInstance(); 938 | if (this.normalizer.getTimeBase() != null) { 939 | String[] ini = this.normalizer.getTimeBase().split("-"); 940 | c.set(Integer.valueOf(ini[0]).intValue(), Integer.valueOf(ini[1]).intValue() - 1, Integer.valueOf(ini[2]).intValue() 941 | , Integer.valueOf(ini[3]).intValue(), Integer.valueOf(ini[4]).intValue(), Integer.valueOf(ini[5]).intValue()); 942 | // LOGGER.debug(DateUtil.formatDateDefault(c.getTime())); 943 | } 944 | 945 | int curTime = c.get(TUNIT_MAP.get(checkTimeIndex)); 946 | if (curTime < _tp.tunit[checkTimeIndex]) { 947 | return; 948 | } 949 | //准备增加的时间单位是被检查的时间的上一级,将上一级时间+1 950 | int addTimeUnit = TUNIT_MAP.get(checkTimeIndex - 1); 951 | c.add(addTimeUnit, 1); 952 | 953 | // _tp.tunit[checkTimeIndex - 1] = c.get(TUNIT_MAP.get(checkTimeIndex - 1)); 954 | for (int i = 0; i < checkTimeIndex; i++) { 955 | _tp.tunit[i] = c.get(TUNIT_MAP.get(i)); 956 | if (TUNIT_MAP.get(i) == Calendar.MONTH) { 957 | ++_tp.tunit[i]; 958 | } 959 | } 960 | 961 | } 962 | 963 | /** 964 | * 如果用户选项是倾向于未来时间,检查所指的day_of_week是否是过去的时间,如果是的话,设为下周。 965 | *

966 | * 如在周五说:周一开会,识别为下周一开会 967 | * 968 | * @param weekday 识别出是周几(范围1-7) 969 | */ 970 | private void preferFutureWeek(int weekday, Calendar c) { 971 | /**1. 确认用户选项*/ 972 | if (!normalizer.isPreferFuture()) { 973 | return; 974 | } 975 | /**2. 检查被检查的时间级别之前,是否没有更高级的已经确定的时间,如果有,则不进行倾向处理.*/ 976 | int checkTimeIndex = 2; 977 | for (int i = 0; i < checkTimeIndex; i++) { 978 | if (_tp.tunit[i] != -1) return; 979 | } 980 | /**获取当前是在周几,如果识别到的时间小于当前时间,则识别时间为下一周*/ 981 | Calendar curC = Calendar.getInstance(); 982 | if (this.normalizer.getTimeBase() != null) { 983 | String[] ini = this.normalizer.getTimeBase().split("-"); 984 | curC.set(Integer.valueOf(ini[0]).intValue(), Integer.valueOf(ini[1]).intValue() - 1, Integer.valueOf(ini[2]).intValue() 985 | , Integer.valueOf(ini[3]).intValue(), Integer.valueOf(ini[4]).intValue(), Integer.valueOf(ini[5]).intValue()); 986 | } 987 | int curWeekday = curC.get(Calendar.DAY_OF_WEEK); 988 | if (weekday == 1) { 989 | weekday = 7; 990 | } 991 | if (curWeekday < weekday) { 992 | return; 993 | } 994 | //准备增加的时间单位是被检查的时间的上一级,将上一级时间+1 995 | c.add(Calendar.WEEK_OF_YEAR, 1); 996 | } 997 | 998 | /** 999 | * 根据上下文时间补充时间信息 1000 | */ 1001 | private void checkContextTime(int checkTimeIndex) { 1002 | for (int i = 0; i < checkTimeIndex; i++) { 1003 | if (_tp.tunit[i] == -1 && _tp_origin.tunit[i] != -1) { 1004 | _tp.tunit[i] = _tp_origin.tunit[i]; 1005 | } 1006 | } 1007 | /**在处理小时这个级别时,如果上文时间是下午的且下文没有主动声明小时级别以上的时间,则也把下文时间设为下午*/ 1008 | if (isFirstTimeSolveContext == true && checkTimeIndex == 3 && _tp_origin.tunit[checkTimeIndex] >= 12 && _tp.tunit[checkTimeIndex] < 12) { 1009 | _tp.tunit[checkTimeIndex] += 12; 1010 | } 1011 | isFirstTimeSolveContext = false; 1012 | } 1013 | 1014 | private static Map TUNIT_MAP = new HashMap<>(); 1015 | 1016 | static { 1017 | TUNIT_MAP.put(0, Calendar.YEAR); 1018 | TUNIT_MAP.put(1, Calendar.MONTH); 1019 | TUNIT_MAP.put(2, Calendar.DAY_OF_MONTH); 1020 | TUNIT_MAP.put(3, Calendar.HOUR_OF_DAY); 1021 | TUNIT_MAP.put(4, Calendar.MINUTE); 1022 | TUNIT_MAP.put(5, Calendar.SECOND); 1023 | } 1024 | } 1025 | -------------------------------------------------------------------------------- /src/com/time/nlp/stringPreHandlingModule.java: -------------------------------------------------------------------------------- 1 | package com.time.nlp; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | /** 7 | * 字符串预处理模块,为分析器TimeNormalizer提供相应的字符串预处理服务 8 | * 9 | * @author 曹零07300720158 10 | * 11 | */ 12 | public class stringPreHandlingModule { 13 | 14 | /** 15 | * 该方法删除一字符串中所有匹配某一规则字串 16 | * 可用于清理一个字符串中的空白符和语气助词 17 | * 18 | * @param target 待处理字符串 19 | * @param rules 删除规则 20 | * @return 清理工作完成后的字符串 21 | */ 22 | public static String delKeyword(String target, String rules){ 23 | Pattern p = Pattern.compile(rules); 24 | Matcher m = p.matcher(target); 25 | StringBuffer sb = new StringBuffer(); 26 | boolean result = m.find(); 27 | while(result) { 28 | m.appendReplacement(sb, ""); 29 | result = m.find(); 30 | } 31 | m.appendTail(sb); 32 | String s = sb.toString(); 33 | //System.out.println("字符串:"+target+" 的处理后字符串为:" +sb); 34 | return s; 35 | } 36 | 37 | /** 38 | * 该方法可以将字符串中所有的用汉字表示的数字转化为用阿拉伯数字表示的数字 39 | * 如"这里有一千两百个人,六百零五个来自中国"可以转化为 40 | * "这里有1200个人,605个来自中国" 41 | * 此外添加支持了部分不规则表达方法 42 | * 如两万零六百五可转化为20650 43 | * 两百一十四和两百十四都可以转化为214 44 | * 一六零加一五八可以转化为160+158 45 | * 该方法目前支持的正确转化范围是0-99999999 46 | * 该功能模块具有良好的复用性 47 | * 48 | * @param target 待转化的字符串 49 | * @return 转化完毕后的字符串 50 | */ 51 | public static String numberTranslator(String target){ 52 | Pattern p = Pattern.compile("[一二两三四五六七八九123456789]万[一二两三四五六七八九123456789](?!(千|百|十))"); 53 | Matcher m = p.matcher(target); 54 | StringBuffer sb = new StringBuffer(); 55 | boolean result = m.find(); 56 | while(result) { 57 | String group = m.group(); 58 | String[] s = group.split("万"); 59 | int num = 0; 60 | if(s.length == 2){ 61 | num += wordToNumber(s[0])*10000 + wordToNumber(s[1])*1000; 62 | } 63 | m.appendReplacement(sb, Integer.toString(num)); 64 | result = m.find(); 65 | } 66 | m.appendTail(sb); 67 | target = sb.toString(); 68 | 69 | p = Pattern.compile("[一二两三四五六七八九123456789]千[一二两三四五六七八九123456789](?!(百|十))"); 70 | m = p.matcher(target); 71 | sb = new StringBuffer(); 72 | result = m.find(); 73 | while(result) { 74 | String group = m.group(); 75 | String[] s = group.split("千"); 76 | int num = 0; 77 | if(s.length == 2){ 78 | num += wordToNumber(s[0])*1000 + wordToNumber(s[1])*100; 79 | } 80 | m.appendReplacement(sb, Integer.toString(num)); 81 | result = m.find(); 82 | } 83 | m.appendTail(sb); 84 | target = sb.toString(); 85 | 86 | p = Pattern.compile("[一二两三四五六七八九123456789]百[一二两三四五六七八九123456789](?!十)"); 87 | m = p.matcher(target); 88 | sb = new StringBuffer(); 89 | result = m.find(); 90 | while(result) { 91 | String group = m.group(); 92 | String[] s = group.split("百"); 93 | int num = 0; 94 | if(s.length == 2){ 95 | num += wordToNumber(s[0])*100 + wordToNumber(s[1])*10; 96 | } 97 | m.appendReplacement(sb, Integer.toString(num)); 98 | result = m.find(); 99 | } 100 | m.appendTail(sb); 101 | target = sb.toString(); 102 | 103 | p = Pattern.compile("[零一二两三四五六七八九]"); 104 | m = p.matcher(target); 105 | sb = new StringBuffer(); 106 | result = m.find(); 107 | while(result) { 108 | m.appendReplacement(sb, Integer.toString(wordToNumber(m.group()))); 109 | result = m.find(); 110 | } 111 | m.appendTail(sb); 112 | target = sb.toString(); 113 | 114 | p = Pattern.compile("(?<=(周|星期))[末天日]"); 115 | m = p.matcher(target); 116 | sb = new StringBuffer(); 117 | result = m.find(); 118 | while(result) { 119 | m.appendReplacement(sb, Integer.toString(wordToNumber(m.group()))); 120 | result = m.find(); 121 | } 122 | m.appendTail(sb); 123 | target = sb.toString(); 124 | 125 | p = Pattern.compile("(? 3 | * 4 | * Description: calendarCommon
5 | * 6 | * Modified log:
7 | * ------------------------------------------------------
8 | * Ver. Date Author Description
9 | * ------------------------------------------------------
10 | * 1.0 2016年4月25日 kexm created.
11 | */ 12 | package com.time.util; 13 | 14 | import java.text.ParseException; 15 | import java.text.SimpleDateFormat; 16 | import java.util.Calendar; 17 | import java.util.Date; 18 | 19 | /** 20 | *

21 | * 日期工具类(来自公司公共项目) 22 | *

23 | * @author kexm 24 | * @version 25 | * @since 2016年4月25日 26 | * 27 | */ 28 | public class CommonDateUtil { 29 | private static String defaultDatePattern = "yyyy-MM-dd"; 30 | public static final long ONE_MINUTE_MILLISECOND = 60000L; 31 | public static final long ONE_HOUR_MILLISECOND = 3600000L; 32 | public static final long ONE_DAY_MILLISECOND = 86400000L; 33 | public static final long ONE_WEEK_MILLISECOND = 604800000L; 34 | public static final long ONE_MONTH_MILLISECOND = 2592000000L; 35 | public static final long ONE_YEAR_MILLISECOND = 31536000000L; 36 | private static final String[] SMART_DATE_FORMATS = { "yyyy-MM-dd HH:mm:ss", "yyyy.MM.dd HH:mm:ss", 37 | "yyyy-MM-dd HH:mm", "yyyy.MM.dd HH:mm", "yyyyMMddHHmmss", "yyyyMMddHHmm", "yyyy-MM-dd", "yyyy.MM.dd", 38 | "yyyyMMdd" }; 39 | 40 | public static final String[] zodiacArray = { "猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊" }; 41 | 42 | public static final String[] constellationArray = { "水瓶座", "双鱼座", "牡羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", 43 | "天蝎座", "射手座", "魔羯座" }; 44 | 45 | private static final int[] constellationEdgeDay = { 20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22 }; 46 | 47 | public static String getDatePattern() { 48 | return defaultDatePattern; 49 | } 50 | 51 | public static int getYear(Date date) { 52 | return getCalendar(date).get(1); 53 | } 54 | 55 | public static int getMonth(Date date) { 56 | return getCalendar(date).get(2); 57 | } 58 | 59 | public static int getDay(Date date) { 60 | return getCalendar(date).get(5); 61 | } 62 | 63 | public static int getWeek(Date date) { 64 | return getCalendar(date).get(7); 65 | } 66 | 67 | public static int getWeekOfFirstDayOfMonth(Date date) { 68 | return getWeek(getFirstDayOfMonth(date)); 69 | } 70 | 71 | public static int getWeekOfLastDayOfMonth(Date date) { 72 | return getWeek(getLastDayOfMonth(date)); 73 | } 74 | 75 | public static final Date parseDate(String strDate, String format) { 76 | SimpleDateFormat df = new SimpleDateFormat(format); 77 | try { 78 | return df.parse(strDate); 79 | } catch (ParseException pe) { 80 | } 81 | return null; 82 | } 83 | 84 | public static final Date parseDateSmart(String strDate) { 85 | if (StringUtil.isEmpty(strDate)) 86 | return null; 87 | for (String fmt : SMART_DATE_FORMATS) { 88 | Date d = parseDate(strDate, fmt); 89 | if (d != null) { 90 | String s = formatDate(d, fmt); 91 | if (strDate.equals(s)) 92 | return d; 93 | } 94 | } 95 | try { 96 | long time = Long.parseLong(strDate); 97 | return new Date(time); 98 | } catch (Exception e) { 99 | } 100 | return null; 101 | } 102 | 103 | public static Date parseDate(String strDate) { 104 | return parseDate(strDate, getDatePattern()); 105 | } 106 | 107 | public static boolean isLeapYear(int year) { 108 | if (year / 4 * 4 != year) { 109 | return false; 110 | } 111 | if (year / 100 * 100 != year) { 112 | return true; 113 | } 114 | 115 | return (year / 400 * 400 == year); 116 | } 117 | 118 | public static boolean isWeekend(Date date) { 119 | Calendar c = Calendar.getInstance(); 120 | if (date != null) 121 | c.setTime(date); 122 | int weekDay = c.get(7); 123 | return ((weekDay == 1) || (weekDay == 7)); 124 | } 125 | 126 | public static boolean isWeekend() { 127 | return isWeekend(null); 128 | } 129 | 130 | public static String getCurrentTime() { 131 | return formatDate(new Date()); 132 | } 133 | 134 | public static String getCurrentTime(String format) { 135 | return formatDate(new Date(), format); 136 | } 137 | 138 | public static String formatDate(Date date, String format) { 139 | if (date == null) 140 | date = new Date(); 141 | if (format == null) 142 | format = getDatePattern(); 143 | SimpleDateFormat formatter = new SimpleDateFormat(format); 144 | return formatter.format(date); 145 | } 146 | 147 | public static String formatDate(Date date) { 148 | long offset = System.currentTimeMillis() - date.getTime(); 149 | String pos = "前"; 150 | if (offset < 0L) { 151 | pos = "后"; 152 | offset = -offset; 153 | } 154 | if (offset >= 31536000000L) { 155 | return formatDate(date, getDatePattern()); 156 | } 157 | if (offset >= 5184000000L) { 158 | return ((offset + 1296000000L) / 2592000000L) + "个月" + pos; 159 | } 160 | if (offset > 604800000L) { 161 | return ((offset + 302400000L) / 604800000L) + "周" + pos; 162 | } 163 | if (offset > 86400000L) { 164 | return ((offset + 43200000L) / 86400000L) + "天" + pos; 165 | } 166 | if (offset > 3600000L) { 167 | return ((offset + 1800000L) / 3600000L) + "小时" + pos; 168 | } 169 | if (offset > 60000L) { 170 | return ((offset + 30000L) / 60000L) + "分钟" + pos; 171 | } 172 | return (offset / 1000L) + "秒" + pos; 173 | } 174 | 175 | public static Date getCleanDay(Date day) { 176 | return getCleanDay(getCalendar(day)); 177 | } 178 | 179 | public static Calendar getCalendar(Date day) { 180 | Calendar c = Calendar.getInstance(); 181 | if (day != null) 182 | c.setTime(day); 183 | return c; 184 | } 185 | 186 | private static Date getCleanDay(Calendar c) { 187 | c.set(11, 0); 188 | c.clear(12); 189 | c.clear(13); 190 | c.clear(14); 191 | return c.getTime(); 192 | } 193 | 194 | public static Date makeDate(int year, int month, int day) { 195 | Calendar c = Calendar.getInstance(); 196 | getCleanDay(c); 197 | c.set(1, year); 198 | c.set(2, month - 1); 199 | c.set(5, day); 200 | return c.getTime(); 201 | } 202 | 203 | private static Date getFirstCleanDay(int datePart, Date date) { 204 | Calendar c = Calendar.getInstance(); 205 | if (date != null) 206 | c.setTime(date); 207 | c.set(datePart, 1); 208 | return getCleanDay(c); 209 | } 210 | 211 | private static Date add(int datePart, int detal, Date date) { 212 | Calendar c = Calendar.getInstance(); 213 | if (date != null) 214 | c.setTime(date); 215 | c.add(datePart, detal); 216 | return c.getTime(); 217 | } 218 | 219 | public static Date getFirstDayOfWeek(Date date) { 220 | return getFirstCleanDay(7, date); 221 | } 222 | 223 | public static Date getFirstDayOfWeek() { 224 | return getFirstDayOfWeek(null); 225 | } 226 | 227 | public static Date getFirstDayOfMonth(Date date) { 228 | return getFirstCleanDay(5, date); 229 | } 230 | 231 | public static Date getFirstDayOfMonth() { 232 | return getFirstDayOfMonth(null); 233 | } 234 | 235 | public static Date getLastDayOfMonth() { 236 | return getLastDayOfMonth(null); 237 | } 238 | 239 | public static Date getLastDayOfMonth(Date date) { 240 | Calendar c = getCalendar(getFirstDayOfMonth(date)); 241 | c.add(2, 1); 242 | c.add(5, -1); 243 | return getCleanDay(c); 244 | } 245 | 246 | public static Date getFirstDayOfSeason(Date date) { 247 | Date d = getFirstDayOfMonth(date); 248 | int delta = getMonth(d) % 3; 249 | if (delta > 0) 250 | d = getDateAfterMonths(d, -delta); 251 | return d; 252 | } 253 | 254 | public static Date getFirstDayOfSeason() { 255 | return getFirstDayOfMonth(null); 256 | } 257 | 258 | public static Date getFirstDayOfYear(Date date) { 259 | return makeDate(getYear(date), 1, 1); 260 | } 261 | 262 | public static Date getFirstDayOfYear() { 263 | return getFirstDayOfYear(new Date()); 264 | } 265 | 266 | public static Date getDateAfterWeeks(Date start, int weeks) { 267 | return getDateAfterMs(start, weeks * 604800000L); 268 | } 269 | 270 | public static Date getDateAfterMonths(Date start, int months) { 271 | return add(2, months, start); 272 | } 273 | 274 | public static Date getDateAfterYears(Date start, int years) { 275 | return add(1, years, start); 276 | } 277 | 278 | public static Date getDateAfterDays(Date start, int days) { 279 | return getDateAfterMs(start, days * 86400000L); 280 | } 281 | 282 | public static Date getDateAfterMs(Date start, long ms) { 283 | return new Date(start.getTime() + ms); 284 | } 285 | 286 | public static long getPeriodNum(Date start, Date end, long msPeriod) { 287 | return (getIntervalMs(start, end) / msPeriod); 288 | } 289 | 290 | public static long getIntervalMs(Date start, Date end) { 291 | return (end.getTime() - start.getTime()); 292 | } 293 | 294 | public static int getIntervalDays(Date start, Date end) { 295 | return (int) getPeriodNum(start, end, 86400000L); 296 | } 297 | 298 | public static int getIntervalWeeks(Date start, Date end) { 299 | return (int) getPeriodNum(start, end, 604800000L); 300 | } 301 | 302 | public static boolean before(Date base, Date date) { 303 | return ((date.before(base)) || (date.equals(base))); 304 | } 305 | 306 | public static boolean after(Date base, Date date) { 307 | return ((date.after(base)) || (date.equals(base))); 308 | } 309 | 310 | public static Date max(Date date1, Date date2) { 311 | if (date1.getTime() > date2.getTime()) { 312 | return date1; 313 | } 314 | return date2; 315 | } 316 | 317 | public static Date min(Date date1, Date date2) { 318 | if (date1.getTime() < date2.getTime()) { 319 | return date1; 320 | } 321 | return date2; 322 | } 323 | 324 | public static boolean inPeriod(Date start, Date end, Date date) { 325 | return ((((end.after(date)) || (end.equals(date)))) && (((start.before(date)) || (start.equals(date))))); 326 | } 327 | 328 | public static String date2Zodica(Date time) { 329 | Calendar c = Calendar.getInstance(); 330 | c.setTime(time); 331 | return year2Zodica(c.get(1)); 332 | } 333 | 334 | public static String year2Zodica(int year) { 335 | return zodiacArray[(year % 12)]; 336 | } 337 | 338 | public static String date2Constellation(Date time) { 339 | Calendar c = Calendar.getInstance(); 340 | c.setTime(time); 341 | int month = c.get(2); 342 | int day = c.get(5); 343 | if (day < constellationEdgeDay[month]) { 344 | --month; 345 | } 346 | if (month >= 0) { 347 | return constellationArray[month]; 348 | } 349 | 350 | return constellationArray[11]; 351 | } 352 | 353 | public static void main(String[] args) { 354 | System.out.println(year2Zodica(1973)); 355 | System.out.println(date2Zodica(new Date())); 356 | System.out.println(date2Constellation(makeDate(1973, 5, 12))); 357 | System.out.println(Calendar.getInstance() == Calendar.getInstance()); 358 | System.out.println(getCleanDay(new Date())); 359 | System.out.println(new Date()); 360 | Calendar c = Calendar.getInstance(); 361 | c.set(5, 1); 362 | System.out.println(getFirstDayOfMonth()); 363 | System.out.println(getLastDayOfMonth(makeDate(1996, 2, 1))); 364 | 365 | System.out.println(formatDate(makeDate(2009, 5, 1))); 366 | System.out.println(formatDate(makeDate(2010, 5, 1))); 367 | System.out.println(formatDate(makeDate(2010, 12, 21))); 368 | System.out.println(before(makeDate(2009, 5, 1), new Date())); 369 | System.out.println(after(makeDate(2009, 5, 1), new Date())); 370 | System.out.println(inPeriod(makeDate(2009, 11, 24), makeDate(2009, 11, 30), makeDate(2009, 11, 25))); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/com/time/util/DateUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 21CN.COM . All rights reserved.
3 | * 4 | * Description: calendar
5 | * 6 | * Modified log:
7 | * ------------------------------------------------------
8 | * Ver. Date Author Description
9 | * ------------------------------------------------------
10 | * 1.0 2016年3月8日 kexm created.
11 | */ 12 | package com.time.util; 13 | 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.text.SimpleDateFormat; 18 | import java.util.ArrayList; 19 | import java.util.Calendar; 20 | import java.util.Date; 21 | import java.util.List; 22 | 23 | /** 24 | *

25 | * 日程项目的时间工具类,继承公司的公共时间工具类 26 | *

27 | * 28 | * @author kexm 29 | * @since 2016年3月8日 30 | */ 31 | public class DateUtil extends CommonDateUtil { 32 | private static final Logger LOGGER = LoggerFactory.getLogger(DateUtil.class); 33 | 34 | /** 35 | * 是否是今天 36 | * 37 | * @param date 38 | * @return 39 | */ 40 | public static boolean isToday(final Date date) { 41 | return isTheDay(date, new Date()); 42 | } 43 | 44 | /** 45 | * 是否是指定日期 46 | * 47 | * @param date 48 | * @param day 49 | * @return 50 | */ 51 | public static boolean isTheDay(final Date date, final Date day) { 52 | return date.getTime() >= dayBegin(day).getTime() && date.getTime() <= dayEnd(day).getTime(); 53 | } 54 | 55 | /** 56 | * 对时间中的分钟向上取整 57 | * 58 | * @param date 59 | * @param round 取整的值 60 | * @return 61 | */ 62 | public static Date roundMin(final Date date, int round) { 63 | if (round > 60 || round < 0) { 64 | round = 0; 65 | } 66 | Calendar c = Calendar.getInstance(); 67 | c.setTime(date); 68 | int min = c.get(Calendar.MINUTE); 69 | if ((min % round) >= (round / 2)) { 70 | min = round * (min / (round + 1)); 71 | } else { 72 | min = round * (min / round); 73 | } 74 | c.set(Calendar.MINUTE, min); 75 | c.set(Calendar.SECOND, 0); 76 | return c.getTime(); 77 | } 78 | 79 | /** 80 | * 获得指定时间那天的某个小时(24小时制)的整点时间 81 | * 82 | * @param date 83 | * @param hourIn24 84 | * @return 85 | */ 86 | public static Date getSpecificHourInTheDay(final Date date, int hourIn24) { 87 | Calendar c = Calendar.getInstance(); 88 | c.setTime(date); 89 | c.set(Calendar.HOUR_OF_DAY, hourIn24); 90 | c.set(Calendar.MINUTE, 0); 91 | c.set(Calendar.SECOND, 0); 92 | c.set(Calendar.MILLISECOND, 0); 93 | return c.getTime(); 94 | } 95 | 96 | /** 97 | * 得到本周周一 98 | * 99 | * @return Date 100 | */ 101 | public static Date getFirstDayOfWeek(Date date) { 102 | Calendar c = Calendar.getInstance(); 103 | c.setTime(date); 104 | int day_of_week = c.get(Calendar.DAY_OF_WEEK) - 1; 105 | if (day_of_week == 0) 106 | day_of_week = 7; 107 | c.add(Calendar.DATE, -day_of_week + 1); 108 | return c.getTime(); 109 | } 110 | 111 | /** 112 | * 处理相对日期的相对运算(当前只支持周、月) 113 | * 114 | * @param date 115 | * @param hourIn24 116 | * @return 117 | */ 118 | public static Date getRelativeTime(final Date date, final int calUnit, final int relative) { 119 | Calendar c = Calendar.getInstance(); 120 | c.setTime(date); 121 | c.add(calUnit, relative); 122 | return c.getTime(); 123 | } 124 | 125 | /** 126 | * 获取指定时间的那天 00:00:00.000 的时间 127 | * 128 | * @param date 129 | * @return 130 | */ 131 | public static Date dayBegin(final Date date) { 132 | return getSpecificHourInTheDay(date, 0); 133 | } 134 | 135 | /** 136 | * 获取指定时间的那天 23:59:59.999 的时间 137 | * 138 | * @param date 139 | * @return 140 | */ 141 | public static Date dayEnd(final Date date) { 142 | Calendar c = Calendar.getInstance(); 143 | c.setTime(date); 144 | c.set(Calendar.HOUR_OF_DAY, 23); 145 | c.set(Calendar.MINUTE, 59); 146 | c.set(Calendar.SECOND, 59); 147 | c.set(Calendar.MILLISECOND, 999); 148 | return c.getTime(); 149 | } 150 | 151 | /** 152 | * 默认时间格式化 153 | * 154 | * @param date 155 | * @param format 156 | * @return 157 | */ 158 | public static String formatDateDefault(Date date) { 159 | return DateUtil.formatDate(date, "yyyy-MM-dd HH:mm:ss"); 160 | } 161 | 162 | /** 163 | * 检测日期格式字符串是否符合format 164 | *

165 | * 主要逻辑为先把字符串parse为该format的Date对象,再将Date对象按format转换为string。如果此string与初始字符串一致,则日期符合format。 166 | *

167 | * 之所以用来回双重逻辑校验,是因为假如把一个非法字符串parse为某format的Date对象是不一定会报错的。 比如 2015-06-29 13:12:121,明显不符合yyyy-MM-dd 168 | * HH:mm:ss,但是可以正常parse成Date对象,但时间变为了2015-06-29 13:14:01。增加多一重校验则可检测出这个问题。 169 | * 170 | * @param strDateTime 171 | * @param format 日期格式 172 | * @return boolean 173 | */ 174 | public static boolean checkDateFormatAndValite(String strDateTime, String format) { 175 | if (strDateTime == null || strDateTime.length() == 0) { 176 | return false; 177 | } 178 | SimpleDateFormat sdf = new SimpleDateFormat(format); 179 | try { 180 | Date ndate = sdf.parse(strDateTime); 181 | String str = sdf.format(ndate); 182 | LOGGER.debug("func strDateTime<" + strDateTime + "> format<" + format + 183 | "> str<" + str + ">"); 184 | if (str.equals(strDateTime)) { 185 | return true; 186 | } else { 187 | return false; 188 | } 189 | } catch (Exception e) { 190 | e.printStackTrace(); 191 | return false; 192 | } 193 | } 194 | 195 | //日期格式为:年 月 日 ;如:2016年04月06日 196 | public static final String FORMAT_CALENDAR_DATE = "yyyy\u5E74MM\u6708dd\u65E5E"; 197 | //时间格式 为:小时:分 ;如:12:30 198 | public static final String FORMAT_CALENDAR_TIME = "HH:mm"; 199 | 200 | 201 | private final static List TIMEUNITS = new ArrayList(); 202 | 203 | static { 204 | TIMEUNITS.add(Calendar.YEAR); 205 | TIMEUNITS.add(Calendar.MONTH); 206 | TIMEUNITS.add(Calendar.DATE); 207 | TIMEUNITS.add(Calendar.HOUR); 208 | TIMEUNITS.add(Calendar.MINUTE); 209 | TIMEUNITS.add(Calendar.SECOND); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/com/time/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 21CN.COM . All rights reserved.
3 | * 4 | * Description: calendarCommon
5 | * 6 | * Modified log:
7 | * ------------------------------------------------------
8 | * Ver. Date Author Description
9 | * ------------------------------------------------------
10 | * 1.0 2016年4月25日 kexm created.
11 | */ 12 | package com.time.util; 13 | 14 | /** 15 | *

16 | * 字符串工具类 17 | *

18 | * @author kexm 19 | * @version 20 | * @since 2016年4月25日 21 | * 22 | */ 23 | public class StringUtil { 24 | 25 | /** 26 | * 字符串是否为空 27 | * @param str 28 | * @return 29 | */ 30 | public static boolean isEmpty(String str) { 31 | return ((str == null) || (str.trim().length() == 0)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /test/com/shinyke/TimeAnalyseTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 21CN.COM . All rights reserved.
3 | * 4 | * Description: TimeNLP
5 | * 6 | * Modified log:
7 | * ------------------------------------------------------
8 | * Ver. Date Author Description
9 | * ------------------------------------------------------
10 | * 1.0 2016年5月4日 kexm created.
11 | */ 12 | package com.shinyke; 13 | 14 | import java.net.URISyntaxException; 15 | import java.net.URL; 16 | import java.util.regex.Matcher; 17 | import java.util.regex.Pattern; 18 | 19 | import org.junit.Test; 20 | 21 | import com.time.nlp.TimeNormalizer; 22 | 23 | /** 24 | *

25 | * 测试类 26 | *

27 | * @author kexm 28 | * @version 1.0 29 | * @since 2016年5月4日 30 | * 31 | */ 32 | public class TimeAnalyseTest { 33 | 34 | @Test 35 | public void testTimeNLP() throws URISyntaxException { 36 | URL url = TimeNormalizer.class.getResource("/TimeExp.m"); 37 | System.out.println(url.toURI().toString()); 38 | TimeNormalizer normalizer = new TimeNormalizer(url.toURI().toString()); 39 | normalizer.setPreferFuture(true); 40 | 41 | // normalizer.parse("Hi,all.下周一下午三点开会");// 抽取时间 42 | // TimeUnit[] unit = normalizer.getTimeUnit(); 43 | // System.out.println("Hi,all.下周一下午三点开会"); 44 | // System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 45 | // 46 | // normalizer.parse("早上六点起床");// 注意此处识别到6天在今天已经过去,自动识别为明早六点(未来倾向,可通过开关关闭:new TimeNormalizer(classPath+"/TimeExp.m", false)) 47 | // unit = normalizer.getTimeUnit(); 48 | // System.out.println("早上六点起床"); 49 | // System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 50 | // 51 | // normalizer.parse("周一开会");// 如果本周已经是周二,识别为下周周一。同理处理各级时间。(未来倾向) 52 | // unit = normalizer.getTimeUnit(); 53 | // System.out.println("周一开会"); 54 | // System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 55 | // 56 | // normalizer.parse("下下周一开会");//对于上/下的识别 57 | // unit = normalizer.getTimeUnit(); 58 | // System.out.println("下下周一开会"); 59 | // System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 60 | // 61 | // normalizer.parse("6:30 起床");// 严格时间格式的识别 62 | // unit = normalizer.getTimeUnit(); 63 | // System.out.println("6:30 起床"); 64 | // System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 65 | // 66 | // normalizer.parse("6-3 春游");// 严格时间格式的识别 67 | // unit = normalizer.getTimeUnit(); 68 | // System.out.println("6-3 春游"); 69 | // System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 70 | // 71 | // normalizer.parse("6月3春游");// 残缺时间的识别 (打字输入时可便捷用户) 72 | // unit = normalizer.getTimeUnit(); 73 | // System.out.println("6月3春游"); 74 | // System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 75 | // 76 | // normalizer.parse("明天早上跑步");// 模糊时间范围识别(可在RangeTimeEnum中修改 77 | // unit = normalizer.getTimeUnit(); 78 | // System.out.println("明天早上跑步"); 79 | // System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 80 | // 81 | // normalizer.parse("本周日到下周日出差");// 多时间识别 82 | // unit = normalizer.getTimeUnit(); 83 | // System.out.println("本周日到下周日出差"); 84 | // System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 85 | // System.out.println(DateUtil.formatDateDefault(unit[1].getTime()) + "-" + unit[1].getIsAllDayTime()); 86 | // 87 | // normalizer.parse("周四下午三点到五点开会");// 多时间识别,注意第二个时间点用了第一个时间的上文 88 | // unit = normalizer.getTimeUnit(); 89 | // System.out.println("周四下午三点到五点开会"); 90 | // System.out.println(DateUtil.formatDateDefault(unit[0].getTime()) + "-" + unit[0].getIsAllDayTime()); 91 | // System.out.println(DateUtil.formatDateDefault(unit[1].getTime()) + "-" + unit[1].getIsAllDayTime()); 92 | // 93 | // //新闻随机抽取长句识别(2016年6月7日新闻,均以当日0点为基准时间计算) 94 | // //例1 95 | // normalizer.parse("7月 10日晚上7 点左右,六安市公安局裕安分局平桥派出所接到辖区居民戴某报警称,到同学家玩耍的女儿迟迟未归,手机也打不通了。很快,派出所又接到与戴某同住一小区的王女士报警:下午5点左右,12岁的儿子和同学在家中吃过晚饭后," 96 | // + "带着3 岁的弟弟一起出了门,之后便没了消息,手机也关机了。短时间内,接到两起孩子失联的报警,值班民警张晖和队友立即前往小区。", "2016-07-19-00-00-00"); 97 | // unit = normalizer.getTimeUnit(); 98 | // System.out.println("7月 10日晚上7 点左右,六安市公安局裕安分局平桥派出所接到辖区居民戴某报警称,到同学家玩耍的女儿迟迟未归,手机也打不通了。很快,派出所又接到与戴某同住一小区的王女士报警:下午5点左右,12岁的儿子和同学在家中吃过晚饭后," 99 | // + "带着3 岁的弟弟一起出了门,之后便没了消息,手机也关机了。短时间内,接到两起孩子失联的报警,值班民警张晖和队友立即前往小区。"); 100 | // for(int i = 0; i < unit.length; i++){ 101 | // System.out.println("时间文本:"+unit[i].Time_Expression +",对应时间:"+ DateUtil.formatDateDefault(unit[i].getTime())); 102 | // } 103 | // 104 | // //例2 105 | // normalizer.parse("《辽宁日报》今日报道,7月18日辽宁召开省委常委扩大会,会议从下午两点半开到六点半,主要议题为:落实中央巡视整改要求。", "2016-07-19-00-00-00"); 106 | // unit = normalizer.getTimeUnit(); 107 | // System.out.println("《辽宁日报》今日报道,7月18日辽宁召开省委常委扩大会,会议从下午两点半开到六点半,主要议题为:落实中央巡视整改要求。"); 108 | // for(int i = 0; i < unit.length; i++){ 109 | // System.out.println("时间文本:"+unit[i].Time_Expression +",对应时间:"+ DateUtil.formatDateDefault(unit[i].getTime())); 110 | // } 111 | 112 | //例3 113 | // normalizer.parse("周五下午7点到8点", "2017-07-19-00-00-00"); 114 | // unit = normalizer.getTimeUnit(); 115 | // System.out.println("周五下午7点到8点"); 116 | // for(int i = 0; i < unit.length; i++){ 117 | // System.out.println("时间文本:"+unit[i].Time_Expression +",对应时间:"+ DateUtil.formatDateDefault(unit[i].getTime())); 118 | // } 119 | 120 | } 121 | 122 | 123 | /** 124 | * 修改TimeExp.m文件的内容 125 | */ 126 | @Test 127 | public void editTimeExp(){ 128 | String path = TimeNormalizer.class.getResource("").getPath(); 129 | String classPath = path.substring(0, path.indexOf("/com/time")); 130 | System.out.println(classPath+"/TimeExp.m"); 131 | /**写TimeExp*/ 132 | Pattern p = Pattern.compile("((前|昨|今|明|后)(天|日)?(早|晚)(晨|上|间)?)|(\\d+个?[年月日天][以之]?[前后])|(\\d+个?半?(小时|钟头|h|H))|(半个?(小时|钟头))|(\\d+(分钟|min))|([13]刻钟)|((上|这|本|下)+(周|星期)([一二三四五六七天日]|[1-7])?)|((周|星期)([一二三四五六七天日]|[1-7]))|((早|晚)?([0-2]?[0-9](点|时)半)(am|AM|pm|PM)?)|((早|晚)?(\\d+[::]\\d+([::]\\d+)*)\\s*(am|AM|pm|PM)?)|((早|晚)?([0-2]?[0-9](点|时)[13一三]刻)(am|AM|pm|PM)?)|((早|晚)?(\\d+[时点](\\d+)?分?(\\d+秒?)?)\\s*(am|AM|pm|PM)?)|(大+(前|后)天)|(([零一二三四五六七八九十百千万]+|\\d+)世)|([0-9]?[0-9]?[0-9]{2}\\.((10)|(11)|(12)|([1-9]))\\.((?