有人對 Swift/SwiftUI 程式設計有興趣嗎?

#39 日曆與曆法(Calendar)

有出國經驗的同學都知道,國外時間跟台灣不一樣,有所謂「時區」,例如日本的時區快台灣一小時,而泰國慢台灣一小時。當台灣時間是上午9:00,在日本已經是 10:00,泰國則還在 8:00。

因為地球由西向東自轉,每個國家在地球不同位置,當台灣清晨看到第一縷陽光的時候,日本已經日出過了一小時,而泰國還處在黑夜,一小時後才會看到太陽。

全世界自1885年起,按所在地理位置,劃分為24個時區,並以英國格林威治天文臺時間為基準。例如台灣時區比格林威治標準時(Greenwich Mean Time, 簡稱 GMT)快8小時,表示為 GMT+8,而日本是 GMT+9,泰國是 GMT+7。

1982年之後改用 UTC (Universal Time Coordinated),基準時間改用原子鐘,比原來GMT標準更精準,但時區劃分大致相同,台灣是 UTC+8,日本是 UTC+9,泰國是 UTC+7。

全世界表達時間的形式,其實有很大不同,除了時區之外,還有一些地區使用獨特的「曆法」,例如台灣使用「民國」紀元,日本「平成」「令和」等年號,以及希伯來曆、印度曆、伊斯蘭曆等等,都仍在使用中,讓時間的表示形式變得複雜。

因此,Swift 時間有3個相關物件來解決這個問題:Date 儲存時間值,DateFormatter 可顯示本地格式,Calendar 用來轉換各地曆法。3個物件類型的關係如下圖:



Date 物件類型是最單純的,取得並儲存一個「相對時間值」,就是以格林威治時間2001/01/01 00:00:00為參考原點,相對所得的秒數,是一個實數(Double)值。

DateFormatter 則可依照本地格式,將 Date時間值轉換為字串,或是反過來,將字串轉為時間值。

Calendar 是用來轉換不同的曆法,或是用來取得年、月、日、時、分、秒等數值。

以下範例分別列印3個物件類型所產生的相關資料。
// 1-10c 曆法(Calendar)
// Revised by Heman, 2021/08/01
import Foundation

let 現在 = Date()

let 秒數 = 現在.timeIntervalSinceReferenceDate
print("現在離參考時間點已過\(秒數)秒")

var 本地格式 = DateFormatter()
本地格式.dateStyle = .full
本地格式.timeStyle = .long
print(本地格式.string(from: 現在))

let 年 = Calendar.current.component(.year, from: 現在)
let 月 = Calendar.current.component(.month, from: 現在)
let 日 = Calendar.current.component(.day, from: 現在)
let 星期 = Calendar.current.component(.weekday, from: 現在)
let 時 = Calendar.current.component(.hour, from: 現在)
let 分 = Calendar.current.component(.minute, from: 現在)
let 秒 = Calendar.current.component(.second, from: 現在)
let 星期幾 = ["日", "一", "二", "三", "四", "五", "六"]
print("現在本地時間是\(年)年\(月)月\(日)日 星期\(星期幾[星期-1]) \(時):\(分):\(秒)")


範例程式1-10c分為三段,分別列印 Date 現在離參考點的秒數、DateFormatter 顯示本地化的字串、以及 Calendar 將 Date 分解得到的年、月、日、時、分、秒整數值。

第一段
let 秒數 = 現在.timeIntervalSinceReferenceDate
print("現在離參考時間點已過\(秒數)秒")
印出:現在離參考時間點已過649486547.089934秒,一年是3千多萬秒,現在離2001年已過了20年,所以是6億多秒。

第二段
var 本地格式 = DateFormatter()
本地格式.dateStyle = .full
本地格式.timeStyle = .long
print(本地格式.string(from: 現在))
印出:2021年8月1日 星期日 GMT+8 下午12:55:47

第三段
let 年 = Calendar.current.component(.year, from: 現在)
let 月 = Calendar.current.component(.month, from: 現在)
let 日 = Calendar.current.component(.day, from: 現在)
let 星期 = Calendar.current.component(.weekday, from: 現在)
let 時 = Calendar.current.component(.hour, from: 現在)
let 分 = Calendar.current.component(.minute, from: 現在)
let 秒 = Calendar.current.component(.second, from: 現在)
let 星期幾 = ["日", "一", "二", "三", "四", "五", "六"]
print("現在本地時間是\(年)年\(月)月\(日)日 星期\(星期幾[星期-1]) \(時):\(分):\(秒)")
印出:現在本地時間是2021年8月1日 星期日 12:55:47

Calendar.current 為目前使用的曆法,大部分電腦的預設是格里曆(西曆),要取得年月日時分秒等個別整數值,則是用 component 這個「方法」,輸入的第一個參數值要加句號 .,也就是 .year, .month 等等,是在物件中定義的,所以要加上句號(表示物件「的」)。

Calendar 所得到的 .weekday 數值是以 Sunday = 1, Monday = 2, ... Saturday = 7,這是格里曆的習慣,我們想列印出中文的星期,就需要用陣列做個轉換。
#40 第1單元結語

到目前為止,我們所學的Swift程式設計內容,都是用 print() 輸出到「主控台」,這稱為「文字模式」,是30年前電腦的顯示模式,跟現在我們用的Mac或iPhone「圖形介面」很不一樣,那為什麼還要學「文字模式」呢?

簡單的說,文字模式是圖形介面的基礎,在我們進入第2單元,開始學圖形介面的程式設計之前,如果沒有將第1單元的內容融會貫通,到第2單元恐怕學不到什麼深度,就退回來了。

所以學程式設計必須按部就班,該有的基礎知識必須紮實打好,後面才能走得更遠。更何況筆者並不是將所有Swift基礎知識全部塞在第1單元裡,而是只挑第2, 3單元會用到的基礎。

這類似網路時代的迷思。有些人認為已經有 Youtube、抖音、Instagram 了,誰還要去看書本、文字的東西?大部分的書本知識在 Google 都可以搜尋到了,為什麼還要去背這些?講一段話,看一段影片,只要三五分鐘,但是寫一篇文章,看一本書,要花十倍時間,怎麼都不划算。

實際上,文字是思想與知識的載體,也是所有影音創作的基礎。以電影為例,電影是所有藝術中,涵蓋最廣的表演形式,不但可以講人文歷史,也能融合科技與想像,各學科知識都能派上用場。但是,一部好電影離不開一個好劇本,一切都是從文字劇本開始。

如果看人類過去一萬年大歷史,只有語言沒有文字的民族,因為累積不了文明成果,生存範圍會不斷萎縮。想想古老的部落,用語言交流很快,誰需要用文字慢慢紀錄呢?但歷史告訴我們,懂得用文字的民族,才能讓人民變強、疆域擴大。

所以千萬不要小看文字的力量。

而在如今的數位時代,程式語言則是另一種能夠承載所有知識的媒介。

過去人類用文字進行記錄與創作,可惜文字無法紀錄影像或聲音,所以我們看不到2千年前生活環境,也聽不到祖先講話的口音,只能透過文字揣摩想像。

現在程式語言更進一步,可記錄的媒體更豐富,創作空間更廣大,交流速度更快範圍更遠,程式設計就如同數位時代的印刷術,會讓文明再次躍升,因此,未來「數位文明」必定會從這裡萌芽!年輕朋友們,不要錯過了。
#41 enum(列舉)

在範例1-10c不小心用了一個沒有說明過的新語法,因為在第2單元還會再遇到,故在此補充說明。

在1-10c第二段
var 本地格式 = DateFormatter()
本地格式.dateStyle = .full
本地格式.timeStyle = .long
用var 定義變數「本地格式」是 DateFormatter 物件的一個實例,然後將其屬性 dateStyle 指定為 .full,以及 timeSyle 指定為 .long,在這裏,.full 與 .long 為什麼是變數(屬性)值?這值是在哪裡定義的?

這跟一個新指令 enum 有關,enum 和 struct 類似,都可用來定義新的資料類型。不過兩者還是有差異,struct 可將幾個資料類型組合成一個新類型,而 enum 則用來組合一些常數名稱,只是某個資料類型的局部範圍。我們用個實例來說明。
// 1-10d 郵遞區號(enum)
// Created by Heman, 2021/08/02
enum 郵遞區號: Int {
case 台北 = 100
case 基隆 = 200
case 新竹 = 300
case 台中 = 400
case 彰化 = 500
case 嘉義 = 600
case 台南 = 700
case 高雄 = 800
case 屏東 = 900
}

struct 包裹 {
var 寄件地址: String
var 寄件區號: 郵遞區號
var 收件地址: String
var 收件區號: 郵遞區號

func 集中() -> String {
if 寄件區號 == 收件區號 { return "送本區物流中心"}
switch 收件區號 {
case .台北, .基隆: return "送到北區物流中心"
case .台中, .新竹, .彰化: return "送到中區物流中心"
case .嘉義, .台南, .高雄, .屏東: return "送到南區物流中心"
}
}
}

let 小包 = 包裹(寄件地址: "台北市忠孝東路一段1號", 寄件區號: .台北,
收件地址: "高雄市三多三路99號", 收件區號: .高雄)
print(小包.集中())

這個範例用 enum 定義了一個新類型,稱為「郵遞區號」,是整數的子集合(部分範圍),裡面只有9個元素,用這個資料類型定義了兩個變數「寄件區號」、「收件區號」,變數的值只能從這9個元素中選出(如 .台北 或 100),例如「寄件區號 = .台北」或「if 寄件區號 == 100」。

這裡的 . 同樣有「屬於」的含義,「.台北」實際上就是「郵遞區號.台北」,所以「寄件區號 = .台北」實際上就是「寄件區號 = 郵遞區號.台北」的意思,只不過類型名稱可省略,只要寫「.台北」即可。

enum 是 "enumerate" 的簡寫,通常翻譯為「列舉」,或不勝「枚舉」、「窮舉」法的意思。

實際上 enum 是 Swift 提供我們一種簡便編碼的方法,就像 Unicode 將「字元」編號一樣,enum 可以給任何自行命名的「名稱」加上編號,就像上面「台北」編號為100,「基隆」編號為200。

配合 enum 定義的資料類型,最常用的指令是 switch,用來撰寫「多選一的條件句」,就像在第5課學過的 if-else 是二選一的條件句。

所以我們在「包裹」物件類型定義中,寫了一個名為「集中」的函式(物件方法),用 switch 條件句,根據「收件區號」來判斷要送到哪個物流中心:
    switch 收件區號 {
case .台北, .基隆: return "送到北區物流中心"
case .台中, .新竹, .彰化: return "送到中區物流中心"
case .嘉義, .台南, .高雄, .屏東: return "送到南區物流中心"
}

如果「收件區號」是 .台北 「或」 .基隆 就送到北區物流中心。像這樣,用 .台北 取代編號100,.基隆 取代編號200,對程式設計師來說更容易使用。


事實上,如果不在意編號數字的話,enum 會自動幫我們編號,可省略編號數字,也就是說,上面的enum宣告可以寫成:
enum 郵遞區號 {
case 台北
case 基隆
case 新竹
case 台中
case 彰化
case 嘉義
case 台南
case 高雄
case 屏東
}

只要寫出自行命名的名稱就可以,enum 自動會幫我們編號。

回到範例1-10c,為什麼可以用 .long, .full 當作值,因為 Swift 在 DateFormatter 物件類型中,用 enum 定義了一個多選一的類型 Style,用來選擇日期或時間字串格式的長短(風格):
enum Style {
case none
case short
case medium
case long
case full
}

所以
本地格式.dateStyle = .full
本地格式.timeStyle = .long
應該就容易理解了。
感謝分享. 給高中的孩子們一個學習的方式
許多人酸言酸語,也不見拿出這樣精神來做這些利於他人的事。
至少樓主願意花心思用他的想法來教。
別批評人了啦,看不下去的人就另外在放篇自己認為的完美教學文。
不要只會出一張嘴說三道四的,其他甚麼也沒有。
支持樓主。
感謝樓主的無私分享
期待大作!
我也有興趣~置板凳先~~謝謝您的分享
最近也正在學習Swift,也想更了解前輩對語法的介紹
關閉廣告
文章分享
評分
評分
複製連結
請輸入您要前往的頁數(1 ~ 9)

今日熱門文章 網友點擊推薦!