package org.nutz.lang;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 一些时间相关的帮助函数
*
* @author zozoh(zozohtnt@gmail.com)
*/
public abstract class Times {
/**
* 判断一年是否为闰年,如果给定年份小于1全部为 false
*
* @param year
* 年份,比如 2012 就是二零一二年
* @return 给定年份是否是闰年
*/
public static boolean leapYear(int year) {
if (year < 4)
return false;
return (year % 400 == 0) || (year % 100 != 0 && year % 4 == 0);
}
/**
* 判断某年(不包括自己)之前有多少个闰年
*
* @param year
* 年份,比如 2012 就是二零一二年
* @return 闰年的个数
*/
public static int countLeapYear(int year) {
// 因为要计算年份到公元元年(0001年)的年份跨度,所以减去1
int span = year - 1;
return (span / 4) - (span / 100) + (span / 400);
}
/**
* 将一个秒数(天中),转换成一个如下格式的数组:
*
* <pre>
* [0-23][0-59[-059]
* </pre>
*
* @param sec
* 秒数
* @return 时分秒的数组
*/
public static int[] T(int sec) {
int[] re = new int[3];
re[0] = Math.min(23, sec / 3600);
re[1] = Math.min(59, (sec - (re[0] * 3600)) / 60);
re[2] = Math.min(59, sec - (re[0] * 3600) - (re[1] * 60));
return re;
}
/**
* 将一个时间字符串,转换成一个一天中的绝对秒数
*
* @param ts
* 时间字符串,符合格式 "HH:mm:ss"
* @return 一天中的绝对秒数
*/
public static int T(String ts) {
String[] tss = Strings.splitIgnoreBlank(ts, ":");
if (null != tss && tss.length == 3) {
int hh = Integer.parseInt(tss[0]);
int mm = Integer.parseInt(tss[1]);
int ss = Integer.parseInt(tss[2]);
return hh * 3600 + mm * 60 + ss;
}
throw Lang.makeThrow("Wrong format of time string '%s'", ts);
}
/**
* 返回服务器当前时间
*
* @return 服务器当前时间
*/
public static Date now() {
return new Date(System.currentTimeMillis());
}
private static Pattern _P_TIME = Pattern.compile("^((\\d{2,4})([/\\\\-])(\\d{1,2})([/\\\\-])(\\d{1,2}))?"
+ "(([ T])?"
+ "(\\d{1,2})(:)(\\d{1,2})(:)(\\d{1,2})"
+ "(([.])"
+ "(\\d{1,}))?)?"
+ "(([+-])(\\d{1,2})(:\\d{1,2})?)?"
+ "$");
/**
* 根据默认时区计算时间字符串的绝对毫秒数
*
* @param ds
* 时间字符串
* @return 绝对毫秒数
*
* @see #ams(String, TimeZone)
*/
public static long ams(String ds) {
return ams(ds, null);
}
/**
* 根据字符串得到相对于 "UTC 1970-01-01 00:00:00" 的绝对毫秒数。
* 本函数假想给定的时间字符串是本地时间。所以计算出来结果后,还需要减去时差
*
* 支持的时间格式字符串为:
*
* <pre>
* yyyy-MM-dd HH:mm:ss
* yyyy-MM-dd HH:mm:ss.SSS
* yy-MM-dd HH:mm:ss;
* yy-MM-dd HH:mm:ss.SSS;
* yyyy-MM-dd;
* yy-MM-dd;
* HH:mm:ss;
* HH:mm:ss.SSS;
* </pre>
*
* 时间字符串后面可以跟 +8 或者 +8:00 表示 GMT+8:00 时区。 同理 -9 或者 -9:00 表示 GMT-9:00 时区
*
* @param ds
* 时间字符串
* @param tz
* 你给定的时间字符串是属于哪个时区的
* @return 时间
* @see #_P_TIME
*/
public static long ams(String ds, TimeZone tz) {
Matcher m = _P_TIME.matcher(ds);
if (m.find()) {
int yy = _int(m, 2, 1970);
int MM = _int(m, 4, 1);
int dd = _int(m, 6, 1);
int HH = _int(m, 9, 0);
int mm = _int(m, 11, 0);
int ss = _int(m, 13, 0);
int ms = _int(m, 16, 0);
/*
* zozoh: 先干掉,还是用 SimpleDateFormat 吧,"1980-05-01 15:17:23" 之前的日子
* 得出的时间竟然总是多 30 分钟 long day = (long) D1970(yy, MM, dd); long MS =
* day * 86400000L; MS += (((long) HH) * 3600L + ((long) mm) * 60L +
* ss) * 1000L; MS += (long) ms;
*
* // 如果没有指定时区 ... if (null == tz) { // 那么用字符串中带有的时区信息, if
* (!Strings.isBlank(m.group(17))) { tz =
* TimeZone.getTimeZone(String.format("GMT%s%s:00", m.group(18),
* m.group(19))); // tzOffset = Long.parseLong(m.group(19)) // *
* 3600000L // * (m.group(18).charAt(0) == '-' ? -1 : 1);
*
* } // 如果依然木有,则用系统默认时区 else { tz = TimeZone.getDefault(); } }
*
* // 计算 return MS - tz.getRawOffset() - tz.getDSTSavings();
*/
String str = String.format("%04d-%02d-%02d %02d:%02d:%02d.%03d",
yy,
MM,
dd,
HH,
mm,
ss,
ms);
SimpleDateFormat df = (SimpleDateFormat) DF_DATE_TIME_MS4.clone();
// 那么用字符串中带有的时区信息 ...
if (null == tz && !Strings.isBlank(m.group(17))) {
tz = TimeZone.getTimeZone(String.format("GMT%s%s:00",
m.group(18),
m.group(19)));
}
// 指定时区 ...
if (null != tz)
df.setTimeZone(tz);
// 解析返回
try {
return df.parse(str).getTime();
}
catch (ParseException e) {
throw Lang.wrapThrow(e);
}
}
throw Lang.makeThrow("Unexpect date format '%s'", ds);
}
/**
* 这个接口函数是 1.b.49 提供了,下一版本将改名为 ams,预计在版本 1.b.51 之后被移除
*
* @deprecated since 1.b.49 util 1.b.51
*/
public static long ms(String ds, TimeZone tz) {
return ams(ds, tz);
}
/**
* 返回时间对象在一天中的毫秒数
*
* @param d
* 时间对象
*
* @return 时间对象在一天中的毫秒数
*/
public static long ms(Date d) {
return ms(C(d));
}
/**
* 返回时间对象在一天中的毫秒数
*
* @param c
* 时间对象
*
* @return 时间对象在一天中的毫秒数
*/
public static int ms(Calendar c) {
int ms = c.get(Calendar.HOUR_OF_DAY) * 3600000;
ms += c.get(Calendar.MINUTE) * 60000;
ms += c.get(Calendar.SECOND) * 1000;
ms += c.get(Calendar.MILLISECOND);
return ms;
}
/**
* 返回当前时间在一天中的毫秒数
*
* @return 当前时间在一天中的毫秒数
*/
public static int ms() {
return ms(Calendar.getInstance());
}
/**
* 根据一个当天的绝对毫秒数,得到一个时间字符串,格式为 "HH:mm:ss.EEE"
*
* @param ms
* 当天的绝对毫秒数
* @return 时间字符串
*/
public static String mss(int ms) {
int sec = ms / 1000;
ms = ms - sec * 1000;
return secs((int) sec) + "." + Strings.alignRight(ms, 3, '0');
}
/**
* 根据一个当天的绝对秒数,得到一个时间字符串,格式为 "HH:mm:ss"
*
* @param ms
* 当天的绝对秒数
* @return 时间字符串
*/
public static String secs(int sec) {
int hh = sec / 3600;
sec -= hh * 3600;
int mm = sec / 60;
sec -= mm * 60;
return Strings.alignRight(hh, 2, '0')
+ ":"
+ Strings.alignRight(mm, 2, '0')
+ ":"
+ Strings.alignRight(sec, 2, '0');
}
/**
* 返回时间对象在一天中的秒数
*
* @param d
* 时间对象
*
* @return 时间对象在一天中的秒数
*/
public static int sec(Date d) {
Calendar c = C(d);
int sec = c.get(Calendar.HOUR_OF_DAY) * 3600;
sec += c.get(Calendar.MINUTE) * 60;
sec += c.get(Calendar.SECOND);
return sec;
}
/**
* 返回当前时间在一天中的秒数
*
* @return 当前时间在一天中的秒数
*/
public static int sec() {
return sec(now());
}
/**
* 根据字符串得到时间对象
*
* @param ds
* 时间字符串
* @return 时间
*
* @see #ams(String)
*/
public static Date D(String ds) {
return D(ams(ds));
}
private static int _int(Matcher m, int index, int dft) {
String s = m.group(index);
if (Strings.isBlank(s))
return dft;
return Integer.parseInt(s);
}
// 常量数组,一年每个月多少天
private static final int[] _MDs = new int[]{31,
28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31};
/**
* 计算一个给定日期,距离 1970 年 1 月 1 日有多少天
*
* @param yy
* 年,比如 1999,或者 43
* @param MM
* 月,一月为 1,十二月为 12
* @param dd
* 日,每月一号为 1
* @return 距离 1970 年 1 月 1 日的天数
*/
public static int D1970(int yy, int MM, int dd) {
// 转换成相对公元元年的年份
// 如果给的年份小于 100,那么就认为是从 1970 开始算的年份
int year = (yy < 100 ? yy + 1970 : yy);
// 得到今年之前的基本天数
int day = (year - 1970) * 365;
// 补上闰年天数
day += countLeapYear(year) - countLeapYear(1970);
// 计算今年本月之前的月份
int mi = Math.min(MM - 1, 11);
boolean isLeapYear = leapYear(yy);
for (int i = 0; i < mi; i++) {
day += _MDs[i];
}
// 考虑今年是闰年的情况
if (isLeapYear && MM > 2) {
day++;
}
// 最后加上天数
day += Math.min(dd, _MDs[mi]) - 1;
// 如果是闰年且本月是 2 月
if (isLeapYear && dd == 29) {
day++;
}
// 如果是闰年并且过了二月
return day;
}
/**
* 根据毫秒数得到时间
*
* @param ms
* 时间的毫秒数
* @return 时间
*/
public static Date D(long ms) {
return new Date(ms);
}
/**
* 根据字符串得到时间
*
* <pre>
* 如果你输入了格式为 "yyyy-MM-dd HH:mm:ss"
* 那么会匹配到秒
*
* 如果你输入格式为 "yyyy-MM-dd"
* 相当于你输入了 "yyyy-MM-dd 00:00:00"
* </pre>
*
* @param ds
* 时间字符串
* @return 时间
*/
public static Calendar C(String ds) {
return C(D(ds));
}
/**
* 根据日期对象得到时间
*
* @param d
* 时间对象
* @return 时间
*/
public static Calendar C(Date d) {
return C(d.getTime());
}
/**
* 根据毫秒数得到时间
*
* @param ms
* 时间的毫秒数
* @return 时间
*/
public static Calendar C(long ms) {
Calendar c = Calendar.getInstance();
c.setTimeInMillis(ms);
return c;
}
/**
* 把时间转换成格式为 y-M-d H:m:s.S 的字符串
*
* @param d
* 时间对象
* @return 该时间的字符串形式 , 格式为 y-M-d H:m:s.S
*/
public static String sDTms(Date d) {
return format(DF_DATE_TIME_MS, d);
}
/**
* 把时间转换成格式为 yy-MM-dd HH:mm:ss.SSS 的字符串
*
* @param d
* 时间对象
* @return 该时间的字符串形式 , 格式为 yy-MM-dd HH:mm:ss.SSS
*/
public static String sDTms2(Date d) {
return format(DF_DATE_TIME_MS2, d);
}
/**
* 把时间转换成格式为 yyyy-MM-dd HH:mm:ss 的字符串
*
* @param d
* 时间对象
* @return 该时间的字符串形式 , 格式为 yyyy-MM-dd HH:mm:ss
*/
public static String sDT(Date d) {
return format(DF_DATE_TIME, d);
}
/**
* 把时间转换成格式为 yyyy-MM-dd 的字符串
*
* @param d
* 时间对象
* @return 该时间的字符串形式 , 格式为 yyyy-MM-dd
*/
public static String sD(Date d) {
return format(DF_DATE, d);
}
/**
* 将一个秒数(天中),转换成一个格式为 HH:mm:ss 的字符串
*
* @param sec
* 秒数
* @return 格式为 HH:mm:ss 的字符串
*/
public static String sT(int sec) {
int[] ss = T(sec);
return Strings.alignRight(ss[0], 2, '0')
+ ":"
+ Strings.alignRight(ss[1], 2, '0')
+ ":"
+ Strings.alignRight(ss[2], 2, '0');
}
/**
* 以本周为基础获得某一周的时间范围
*
* @param off
* 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周
*
* @return 时间范围(毫秒级别)
*
* @see org.nutz.ztask.util.ZTasks#weeks(long, int, int)
*/
public static Date[] week(int off) {
return week(System.currentTimeMillis(), off);
}
/**
* 以某周为基础获得某一周的时间范围
*
* @param base
* 基础时间,毫秒
* @param off
* 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周
*
* @return 时间范围(毫秒级别)
*
* @see org.nutz.ztask.util.ZTasks#weeks(long, int, int)
*/
public static Date[] week(long base, int off) {
return weeks(base, off, off);
}
/**
* 以本周为基础获得时间范围
*
* @param offL
* 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周
* @param offR
* 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周
*
* @return 时间范围(毫秒级别)
*
* @see org.nutz.ztask.util.ZTasks#weeks(long, int, int)
*/
public static Date[] weeks(int offL, int offR) {
return weeks(System.currentTimeMillis(), offL, offR);
}
/**
* 按周获得某几周周一 00:00:00 到周六 的时间范围
* <p>
* 它会根据给定的 offL 和 offR 得到一个时间范围
* <p>
* 对本函数来说 week(-3,-5) 和 week(-5,-3) 是一个意思
*
* @param base
* 基础时间,毫秒
* @param offL
* 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周
* @param offR
* 从本周偏移几周, 0 表示本周,-1 表示上一周,1 表示下一周
*
* @return 时间范围(毫秒级别)
*/
public static Date[] weeks(long base, int offL, int offR) {
int from = Math.min(offL, offR);
int len = Math.abs(offL - offR);
// 现在
Calendar c = Calendar.getInstance();
c.setTimeInMillis(base);
Date[] re = new Date[2];
// 计算开始
c.setTimeInMillis(c.getTimeInMillis() + MS_WEEK * from);
c.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
re[0] = c.getTime();
// 计算结束
c.setTimeInMillis(c.getTimeInMillis() + MS_WEEK * (len + 1) - 1000);
c.set(Calendar.HOUR_OF_DAY, 23);
c.set(Calendar.MINUTE, 59);
c.set(Calendar.SECOND, 59);
re[1] = c.getTime();
// 返回
return re;
}
private static final String[] _MMM = new String[]{"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"};
/**
* 将一个时间格式化成容易被人类阅读的格式
*
* <pre>
* 如果 1 分钟内,打印 Just Now
* 如果 1 小时内,打印多少分钟
* 如果 1 天之内,打印多少小时之前
* 如果是今年之内,打印月份和日期
* 否则打印月份和年
* </pre>
*
* @param d
* @return
*/
public static String formatForRead(Date d) {
long ms = System.currentTimeMillis() - d.getTime();
// 如果 1 分钟内,打印 Just Now
if (ms < (60000)) {
return "Just Now";
}
// 如果 1 小时内,打印多少分钟
if (ms < (60 * 60000)) {
return "" + (ms / 60000) + "Min.";
}
// 如果 1 天之内,打印多少小时之前
if (ms < (24 * 3600 * 1000)) {
return "" + (ms / 3600000) + "hr.";
}
// 如果一周之内,打印多少天之前
if (ms < (7 * 24 * 3600 * 1000)) {
return "" + (ms / (24 * 3600000)) + "Day";
}
// 如果是今年之内,打印月份和日期
Calendar c = Calendar.getInstance();
int thisYear = c.get(Calendar.YEAR);
c.setTime(d);
int yy = c.get(Calendar.YEAR);
int mm = c.get(Calendar.MONTH);
if (thisYear == yy) {
int dd = c.get(Calendar.DAY_OF_MONTH);
return String.format("%s %d", _MMM[mm], dd);
}
// 否则打印月份和年
return String.format("%s %d", _MMM[mm], yy);
}
/**
* 以给定的时间格式来安全的对时间进行格式化,并返回格式化后所对应的字符串
*
* @param fmt
* 时间格式
* @param d
* 时间对象
* @return 格式化后的字符串
*/
public static String format(DateFormat fmt, Date d) {
return ((DateFormat) fmt.clone()).format(d);
}
/**
* 以给定的时间格式来安全的对时间进行格式化,并返回格式化后所对应的字符串
*
* @param fmt
* 时间格式
* @param d
* 时间对象
* @return 格式化后的字符串
*/
public static String format(String fmt, Date d) {
return new SimpleDateFormat(fmt).format(d);
}
/**
* 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象(包裹RuntimeException)
*
* @param fmt
* 时间格式
* @param s
* 时间字符串
* @return 该时间字符串对应的时间对象
*/
public static Date parseq(DateFormat fmt, String s) {
try {
return parse(fmt, s);
}
catch (ParseException e) {
throw Lang.wrapThrow(e);
}
}
/**
* 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象(包裹RuntimeException)
*
* @param fmt
* 时间格式
* @param s
* 时间字符串
* @return 该时间字符串对应的时间对象
*/
public static Date parseq(String fmt, String s) {
try {
return parse(fmt, s);
}
catch (ParseException e) {
throw Lang.wrapThrow(e);
}
}
/**
* 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象
*
* @param fmt
* 时间格式
* @param s
* 日期时间字符串
* @return 该时间字符串对应的时间对象
*/
public static Date parse(DateFormat fmt, String s) throws ParseException {
return ((DateFormat) fmt.clone()).parse(s);
}
/**
* 以给定的时间格式来安全的解析时间字符串,并返回解析后所对应的时间对象
*
* @param fmt
* 时间格式
* @param s
* 日期时间字符串
* @return 该时间字符串对应的时间对象
*/
public static Date parse(String fmt, String s) throws ParseException {
return new SimpleDateFormat(fmt).parse(s);
}
private static final DateFormat DF_DATE_TIME_MS = new SimpleDateFormat("y-M-d H:m:s.S");
private static final DateFormat DF_DATE_TIME_MS2 = new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS");
private static final DateFormat DF_DATE_TIME_MS4 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static final DateFormat DF_DATE_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final DateFormat DF_DATE = new SimpleDateFormat("yyyy-MM-dd");
private static final long MS_DAY = 3600L * 24 * 1000;
private static final long MS_WEEK = MS_DAY * 7;
}