日期時間的處理-程式語言

日期時間的處理是每一個程式設計師都會遇到。雖然看起來很簡單,但是在實際使用上,又會因為各程式語言的實作不同而有不同的用法。這裡簡單地記錄我曾經用過的程式語言對於日期時間的處理.

Java 1.7 以前

Java 1.7 以前 - 相關的類別

  • java.util.Date
  • java.util.Calendar
  • java.util.TimeZone
  • java.text.SimpleDateFormat

Java 1.7 以前 - 日期時間的生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

// 取得當下的時間戳記 (timestamp) , 單位為 milliseconds
long timestamp1 = System.currentTimeMillis();

// 使用 Date,取得當下的時間
Date date = new Date();

// 使用 Date 物件, 取得時間戳記
long timestamp2 = date.getTime();

// 使用 Calendar, 取得當下的時間
Calendar calendar = Calendar.getInstance();

// 使用 Calendar, 取得 Date 物件
Date date1 = calendar.getTime();

// 使用 Calendar, 取得時間戳記
calendar.getTimeInMillis()

// 取得特定的日期, 例如 要取得 2017年3月15日
calendar.set(Calendar.YEAR, 2017);
calendar.set(Calendar.MONTH, 3);
calendar.set(Calendar.DAY_OF_MONTH, 15);
Date date2 = calendar.getTime();

Java1.7 以前 - 日期字串的轉換

我們可以使用 SimpleDateFormat 類別來轉換字串及日期.

1
2
3
4
5
6
7
8
9
10

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 從字串轉換成日期
Date date = format.parse("2017-03-12 14:23:12");

// 從日期轉換成字串, 輸出為 2017-03-20 15:33:22
String dateString = format.format(new Date());


以下是比較常用的格式字符,如果要看完整的說明可以看 Java doc 的 SimpleDateFormat 類別

1
2
3
4
5
6
7
8
9
10
11
12
y : 年, 使用 yyyy 為4位數, yy 為二位數
M : 月
d : 日
a : am / pm
H : 時, 24小時制 (0 - 23)
h : 時, 12小時制 (1 -12)
m : 分
s : 秒 (second)
S : 毫秒 (millisecond)
z : 時區, 顯示時區名稱,例如: Pacific Standard Time; PST; GMT-08:00
Z : 時區, 顯示時區時差(RFC 822),例如: -0800

Java 1.7 以前 - 日期時間的運算

Calendar 類別有提供簡易的方法可以加減日期

1
2
3
4
5
6
7
8
9
10
11
12
13
Calendar calendar = Calendar.getInstance();

// 設定日期為 2017-03-15
calendar.set(Calendar.YEAR, 2017);
calendar.set(Calendar.MONTH, 3);
calendar.set(Calendar.DAY_OF_MONTH, 15);

// 將日期減一天, 即為 2017-03-15
calendar.add(Calendar.DATE, -1);

// 將日期加5天, 即為 2017-03-20
calendar.add(Calendar.DATE, 5);

Java 1.7 以前 - 時區

在 Java 1.7 以前,我們可以有以下二種方式來取得 TimeZone 物件:

1
2
3
4
5
6
7
8
9
10
// 經由 Calendar 取得時區
Calendar calendar = Calendar.getInstance();
TimeZone tz1 = calendar.getTimeZone();

// 直接使用 TimeZone 類別産生
// 取得預設的 TimeZone 物件
TimeZone tz2 = TimeZone.getDefault();
// 直接指定 timezone
TimeZone tz3 = TimeZone.getTimeZone("Asia/Taipei");

時區的轉換

1
2
3
4
5
6
// 要將時區轉換成 日本東京的時區

Calendar calendar = Calendar.getInstance();
TimeZone timeZone = TimeZone.getTimeZone("Asia/Tokyo");
calendar.setTimeZone(timeZone);

使用 TimeZone.getAvailableIDs() 可以取得所有的時區列表.

Java 1.8 之後

Java8 新增加的日期 / 時間的類別是基於 (JSR-310)[https://jcp.org/en/jsr/detail?id=310] 的實作.
這些實作都是包含在 java.time 這個 package 之下。主要有以下的優點:

  • 物件的不變性:所有的物件都是 Immutable Object, 在物件被生成後就不能改變其值,也因此這些物件是線程安全 (thread-safe)
  • 類別的明確性:依照不同的使用情況分成 LocalDate , LocalTime , LocalDateTimeZonedDateTime … 等類別.
  • 方法的一致性:在所有的類別中,方法都是具有一致性,相同名稱的方法完成的行為也是相同,例如 now () 方法在所有的類別中,都是産生一個當前的物件.
  • 可擴展性:除了能支援國際通用的 ISO 8601 日期與時間的表示方式外,還支援一些 non-ISO 的曆法.

Java 1.8 之後 - 相關的類別

  • java.time.Instant
  • java.time.Duration
  • java.time.LocalDate
  • java.time.LocalDateTime
  • java.time.LocalTime
  • java.time.ZonedDateTime
  • java.time.format.DateTimeFormatter

Method 的前置詞

of : 靜態的工廠方法,指定日期時間,來産生物件.
parse : 靜態的工廠方法,用來解析成日期時間物件.
get : 取得某些值.
is : 檢查某些條件是否為 True.
with : 産生此物件的複本並將此物件某些值替換.
plus : 産生此物件的複本並加些數量到某些欄位.
minus : 産生此物件的複本並從某些欄位減些數量.
to : 轉換成另一個類別.
at : 將二個物件組合起來.

Java 1.8 之後 - 日期時間的生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 産生 timestamp
Instant now = Instant.now();
long timestamp = now.getEpochSecond();

// 產生不帶時區的日期物件
LocalDate localDate1 = LocalDate.now();
// 指定日期為 2017-3-20 的物件
LocalDate localDate2 = LocalDate.of(2017, 3, 20);

// 產生不帶時區的時間物件
LocalTime localTime1 = LocalTime.now();
// 指定時間為 15:30:20 的日期物件
LocalTime localTime2 = LocalTime.of(15, 30, 20);

// 產生包含日期及時間且不帶時區的物件
LocalDateTime localDateTime1 = LocalDateTime.now();
// 指定時間為 2017-3-20 15:30:20 的物件
LocalDateTime localDateTime2 = LocalDateTime.of(2017, 3, 20, 15, 30, 20, 0);

// 產生包含日期及時間且帶時區的物件
ZonedDateTime zonedDateTime1 = ZonedDateTime.now();

// 指定時間為 2017-3-20 15:30:20 且時區為 Asia/Taipei 的物件
ZoneId zoneId = ZoneId.of("Asia/Taipei");
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(2017, 3, 20, 15, 30, 20, 0, zoneId);

Duration

Duration 類別用來定義一段時間的區段,比較合適的使用場景是用來計算以機器為主的時間,也就是你可以用 Duration 以奈秒,秒,分,時 … 等時間單位來計算.

舉例來說, Duration 可以用來測量一個 API 的執行時間或是一個服務的運行時間,

1
2
3
4
5
6
7
8
Instant start = Instant.now();

// do something

Instant end = Instant.now();

Duration duration = Duration.between(start, end);

Period

PeriodDuration 很像,都是用來定義一段時間的區段。但是比較不一樣的是 Period 的最小時間單位為天 (days). 比較合適用在需要曆法支援的場景,因為一年或是一個月的時間區段不是固定的.

舉例來說, Period 可以用來計算,從某個日期到今天總共經過多少年,多少月,多少天

1
2
3
4
5
6
7
8
9

LocalDate today = LocalDate.now();
// 假設 日期開始為 1990-01-01
LocalDate before = LocalDate.of(1990, Month.JANUARY, 1);

Period p = Period.between(before, today);
int years = p.getYears();
int months = p.getMonths();
int days = p.getDays();

Java 1.8 之後 - 日期字串的轉換

1
2
3
4
5
6
7
8
9
// 把時間轉換成指定的字串
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd’T’HH:mm:ss");
String dateTimeText1 = dateTime.format(formatter);

// 從字串轉成日期
String dateTimeText2 = "2017-03-20T15:30:20";
LocalDateTime parsedDate = LocalDateTime.parse(dateTimeText2, formatter);

Java 1.8 之後 - 日期時間的運算

基本的運算

日期時間類別本身就有提供運算的方法

Instant 類別提供的方法:

  • plusSeconds()
  • plusMillis()
  • plusNanos()
  • minusSeconds()
  • minusMillis()
  • minusNanos()

1
2
3
4
5
6
7
Instant now     = Instant.now();

// 加 10 秒
Instant timestamp1 = now.plusSeconds(10);
// 減 10 秒
Instant timestamp2 = now.minusSeconds(10);

LocalDate 類別提供的方法:

  • plusDays()
  • plusWeeks()
  • plusMonths()
  • plusYears()
  • minusDays()
  • minusWeeks()
  • minusMonths()
  • minusYears()

1
2
3
4
5
6
7
LocalDate localDate = LocalDate.now();

// 加 1 天
LocalDate localDate1 = localDate.plusDays(1);
// 減 3 天
LocalDate localDate2 = localDate.minusDays(3);

LocalTime 類別提供的方法:

  • plusHours()
  • plusMinutes()
  • plusSeconds()
  • plusNanos()
  • minusHours()
  • minusMinutes()
  • minusSeconds()
  • minusNanos()

1
2
3
4
5
6
7
LocalTime localTime = LocalTime.now();

// 加 1 小時
LocalTime localTime1 = localTime.plusHours(1);
//減 1 小時
LocalTime localTime2 = localTime.minusHours(1);

LocalDateTimeZonedDateTime 類別提供的方法:

  • plusYears()
  • plusMonths()
  • plusDays()
  • plusHours()
  • plusMinutes()
  • plusSeconds()
  • plusNanos()
  • minusYears()
  • minusMonths()
  • minusDays()
  • minusHours()
  • minusMinutes()
  • minusSeconds()
  • minusNanos()

1
2
3
4
5
6
7
LocalDateTime localDateTime  = LocalDateTime.now();

// 加 1 天
LocalDateTime localDateTime1 = localDateTime.plusDays(1);
// 減 1 天
LocalDateTime localDateTime2 = localDateTime.minusDays(1);

TemporalAdjusters

TemporalAdjusters 提供一些靜態方法可以讓我們直接從一個時間點轉移到其它的時間點。比如:我們想要取得這個月的最後一天:

1
2
3
LocalDateTime now  = LocalDateTime.now();

LocalDateTime lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth());

Java 1.8 之後 - 時區

1
2
3
4
5
6
7
// 產生包含日期及時間且帶時區的物件
ZonedDateTime zonedDateTime1 = ZonedDateTime.now();

// 把日期時區轉換成 Asia/Tokyo
ZoneId zoneId = ZoneId.of("Asia/Tokyo");
ZonedDateTime zonedDateTime2 = zonedDateTime.withZoneSameInstant(zoneId));

PHP

PHP - 相關的類別

  • DateTime
  • DateTimeZone

PHP - 日期時間的生成

1
2
3
// 産生一個日期時間物件
$datetime = new DateTime();

PHP - 日期字串的轉換

以下是有關時間的格式字符,只列出比較常用的部分,其它詳細的說明,請參照 date

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Y : 年, 用4位數字表示年份, 如 1999, 2017
y : 年, 用2位數字表示年份, 如 99, 17
M : 月, 用英文縮寫表示月份, Jan ~ Dec
m : 月, 數字表示月份, 01 ~ 12
D : 星期, 用英文縮寫表示星期, Mon ~ Sun
l : 星期, 用英文表示星期, Monday ~ Sunday
d : 日, 用2位數字表示日, 01 ~31
A : am / pm
H : 時, 24小時制 (00 - 23)
h : 時, 12小時制 (01 -12)
i : 分, 00 ~ 59
s : 秒, 00 ~ 59
O : 時區, 顯示時區時差,例如: -0800
P : 時區, 顯示時區時差,但是在時和分之間有個分號, 例如: -08:00
T : 時區, 顯示時區名稱,例如: PST


在産生日期時間物件時,可以使用以下格式的字串來産生特定的日期

1
2
3
4
5
6
7
8
9
10
11
12
ATOM : Y-m-d\TH:i:sP (例如 : 2005-08-15T15:52:01+00:00)
COOKIE : l, d-M-Y H:i:s T (例如 : Monday, 15-Aug-2005 15:52:01 UTC)
ISO8601 : Y-m-d\TH:i:sO, (例如 : 2005-08-15T15:52:01+0000)
RFC822 : D, d M y H:i:s O (例如 : Mon, 15 Aug 05 15:52:01 +0000)
RFC850 : l, d-M-y H:i:s T (例如 : Monday, 15-Aug-05 15:52:01 UTC)
RFC1036 : D, d M y H:i:s O (例如 : Mon, 15 Aug 05 15:52:01 +0000)
RFC1123 : D, d M Y H:i:s O (例如 : Mon, 15 Aug 2005 15:52:01 +0000)
RFC2822 : D, d M Y H:i:s O (例如 : Mon, 15 Aug 2005 15:52:01 +0000)
RFC3339 : Y-m-d\TH:i:sP 例如 : 2005-08-15T15:52:01+00:00)
RSS : D, d M Y H:i:s O (例如 : Mon, 15 Aug 2005 15:52:01 +0000)
W3C : Y-m-d\TH:i:sP (例如 : 2005-08-15T15:52:01+00:00)


在 PHP 則只要把符合上述字串的日期時間格式帶入
1
2
3
4
5
6
// 產生日期時間物件
$datetime = new DateTime('2017-03-20T14:20:15+08:00');

// 轉換日期時間為字串
$text = $datetime->format(‘Y-m-d H:i:s’);

PHP - 日期時間的運算

使用 modify 方法可以進行日期的加減

1
2
3
4
5
6
7
8
9
$datetime = new DateTime();

// 可用這幾個關鍵字 year month day hour minute second
// 比如加一個月
$datetime->modify('+1 month');

// 比如減1個小時
$datetime->modify('+1 hour');

PHP - 時區

PHP 有提供 DateTimeZone 這個時區類別,我們可以直接來使用

1
2
3
4
5
6
7
8
9
10
11
// 產生時區物件
$tz1 = new DateTimeZone(‘Asia/Taipei');

// 產生帶有時區的日期時間物件, now 關鍵字是代表現在的時間
$datetime = new DateTime('now',$tz1);

// 產生另一個時區物件
$tz2 = new DateTimeZone('Asia/Tokyo');
// 設定時區為新的時區, 就可以改變原本的時區
$datetime->setTimezone($tz2);

Python

Python 處理日期時間的方式有許多種。但在這裡是以 DateTime 為核心.

Python - 相關的類別

  • datetime
  • datetime.time
  • datetime.timedelta

可以參照 datetime.

Python - 日期時間的生成

1
2
3
4
5
6
7
# 產生一個當前的日期物件
now = datetime.now()

#產生一個特定的日期物件
# year, month, day 是必要的參數而 hour, minute, second, microsecond 及 tzinfo 則是非必要
mydate = datetime.datetiem(2017, 03, 20)

Python - 日期字串的轉換

Python 有提供 strftime()strptime() 這二個方法來轉換日期和字串.

以下是常用的格式字符,其它的字符可以參照 strftime and strptime behavior

1
2
3
4
5
6
7
8
9
10
11
12
13
%y : 年, 用2位數字表示年份, 如 99, 17
%Y : 年, 用4位數字表示年份, 如 1999, 2017
%m : 月, 數字表示月份, 01 ~ 12
%d : 日, 用2位數字表示日, 01 ~31
%H : 時, 24小時制 (00 - 23)
%I : 時, 12小時制 (01 -12)
%M : 分, 00 ~ 59
%S : 秒, 00 ~ 59
%p : am / pm
%z : 時區, 顯示時區時差,例如: -0800
%Z : 時區, 顯示時區名稱,例如: PST
%% : %號

1
2
3
4
5
6
7
8
now = datetime.datetime.now()

# 轉換成字串
text = now.strftime("%Y-%m-%d %H:%M:%S")

# 從字串換轉換成日期
mydate = datetime.datetime.strptime("2017-03-20 15:20:10", "%Y-%m-%d %H:%M:%S")

Python - 日期時間的運算

timedelta 物件可以讓我們對日期時間進行操作,可參照 timedelta-objects

1
2
3
4
5
6
today = datetime.datetime.now()

# timedelta 的參數有提供 days, seconds, microseconds, milliseconds, minutes, hours, weeks, 這些欄位讓我們操作
# 例如要取得昨天的日期
yesterday = today + timedelta(days=-1)

Python - 時區

Python 本身並沒有好用的時區模組,所以這裡會使用第三方模組 pytz

1
2
3
4
5
6
7
8
9
10
11

# 產生時區物件
twtz = pytz.timezone(‘Asia/Taipei’)
jptz = pytz.timezone(‘Asia/Tokyo’)

# 產生一個指定時區的日期時間物件
loc_dt = datetime.datetime(2017, 03, 20, 15, 20, 10, tzinfo=twtz)

# 使用 astimezone 來更換時區
loc_dt.astimezone(jptz)