| author | wenzelm | 
| Tue, 07 Jun 2022 16:47:57 +0200 | |
| changeset 75524 | ff8012edac89 | 
| parent 75394 | 42267c650205 | 
| child 77163 | 7ceed24c88dc | 
| permissions | -rw-r--r-- | 
| 64056 | 1  | 
/* Title: Pure/General/date.scala  | 
2  | 
Author: Makarius  | 
|
3  | 
||
4  | 
Date and time, with time zone.  | 
|
5  | 
*/  | 
|
6  | 
||
7  | 
package isabelle  | 
|
8  | 
||
9  | 
||
| 64096 | 10  | 
import java.util.Locale  | 
| 74943 | 11  | 
import java.time.{Instant, ZonedDateTime, LocalTime, ZoneId}
 | 
| 64056 | 12  | 
import java.time.format.{DateTimeFormatter, DateTimeParseException}
 | 
| 64059 | 13  | 
import java.time.temporal.TemporalAccessor  | 
14  | 
||
15  | 
import scala.annotation.tailrec  | 
|
| 64056 | 16  | 
|
17  | 
||
| 75393 | 18  | 
object Date {
 | 
| 64056 | 19  | 
/* format */  | 
20  | 
||
| 75393 | 21  | 
  object Format {
 | 
22  | 
    def make(fmts: List[DateTimeFormatter], tune: String => String = s => s): Format = {
 | 
|
| 
73120
 
c3589f2dff31
more informative errors: simplify diagnosis of spurious failures reported by users;
 
wenzelm 
parents: 
71601 
diff
changeset
 | 
23  | 
require(fmts.nonEmpty, "no date formats")  | 
| 64059 | 24  | 
|
| 64056 | 25  | 
      new Format {
 | 
| 64059 | 26  | 
def apply(date: Date): String = fmts.head.format(date.rep)  | 
27  | 
def parse(str: String): Date =  | 
|
| 64098 | 28  | 
new Date(ZonedDateTime.from(Formatter.try_variants(fmts, tune(str))))  | 
| 64056 | 29  | 
}  | 
| 64059 | 30  | 
}  | 
| 64056 | 31  | 
|
| 64098 | 32  | 
def apply(pats: String*): Format =  | 
| 71601 | 33  | 
make(pats.toList.map(Date.Formatter.pattern))  | 
| 64060 | 34  | 
|
| 64098 | 35  | 
    val default: Format = Format("dd-MMM-uuuu HH:mm:ss xx")
 | 
36  | 
    val date: Format = Format("dd-MMM-uuuu")
 | 
|
37  | 
    val time: Format = Format("HH:mm:ss")
 | 
|
| 73649 | 38  | 
    val alt_date: Format = Format("uuuuMMdd")
 | 
| 64056 | 39  | 
}  | 
40  | 
||
| 75393 | 41  | 
  abstract class Format private {
 | 
| 64056 | 42  | 
def apply(date: Date): String  | 
43  | 
def parse(str: String): Date  | 
|
| 64098 | 44  | 
|
| 64056 | 45  | 
def unapply(str: String): Option[Date] =  | 
| 64098 | 46  | 
      try { Some(parse(str)) } catch { case _: DateTimeParseException => None }
 | 
| 64056 | 47  | 
}  | 
| 64057 | 48  | 
|
| 75393 | 49  | 
  object Formatter {
 | 
| 64099 | 50  | 
def pattern(pat: String): DateTimeFormatter = DateTimeFormatter.ofPattern(pat)  | 
51  | 
||
52  | 
def variants(pats: List[String], locs: List[Locale] = Nil): List[DateTimeFormatter] =  | 
|
| 75394 | 53  | 
      pats.flatMap { pat =>
 | 
| 64099 | 54  | 
val fmt = pattern(pat)  | 
| 71601 | 55  | 
if (locs.isEmpty) List(fmt) else locs.map(fmt.withLocale)  | 
| 75394 | 56  | 
}  | 
| 64099 | 57  | 
|
| 75393 | 58  | 
@tailrec def try_variants(  | 
59  | 
fmts: List[DateTimeFormatter],  | 
|
60  | 
str: String,  | 
|
61  | 
last_exn: Option[DateTimeParseException] = None  | 
|
62  | 
    ): TemporalAccessor = {
 | 
|
| 64099 | 63  | 
      fmts match {
 | 
64  | 
case Nil =>  | 
|
65  | 
          throw last_exn.getOrElse(new DateTimeParseException("Failed to parse date", str, 0))
 | 
|
66  | 
case fmt :: rest =>  | 
|
67  | 
          try { ZonedDateTime.from(fmt.parse(str)) }
 | 
|
68  | 
          catch { case exn: DateTimeParseException => try_variants(rest, str, Some(exn)) }
 | 
|
69  | 
}  | 
|
70  | 
}  | 
|
71  | 
}  | 
|
72  | 
||
| 64057 | 73  | 
|
| 74925 | 74  | 
/* ordering */  | 
75  | 
||
| 75393 | 76  | 
  object Ordering extends scala.math.Ordering[Date] {
 | 
| 74925 | 77  | 
def compare(date1: Date, date2: Date): Int =  | 
78  | 
date1.instant.compareTo(date2.instant)  | 
|
79  | 
}  | 
|
80  | 
||
| 75393 | 81  | 
  object Rev_Ordering extends scala.math.Ordering[Date] {
 | 
| 74925 | 82  | 
def compare(date1: Date, date2: Date): Int =  | 
83  | 
date2.instant.compareTo(date1.instant)  | 
|
84  | 
}  | 
|
85  | 
||
86  | 
||
| 64057 | 87  | 
/* date operations */  | 
88  | 
||
| 69980 | 89  | 
  def timezone_utc: ZoneId = ZoneId.of("UTC")
 | 
90  | 
  def timezone_berlin: ZoneId = ZoneId.of("Europe/Berlin")
 | 
|
91  | 
||
| 64057 | 92  | 
def timezone(): ZoneId = ZoneId.systemDefault  | 
93  | 
||
94  | 
def now(zone: ZoneId = timezone()): Date = new Date(ZonedDateTime.now(zone))  | 
|
95  | 
||
| 65014 | 96  | 
def instant(t: Instant, zone: ZoneId = timezone()): Date =  | 
97  | 
new Date(ZonedDateTime.ofInstant(t, zone))  | 
|
98  | 
||
99  | 
def apply(t: Time, zone: ZoneId = timezone()): Date = instant(t.instant, zone)  | 
|
| 64056 | 100  | 
}  | 
101  | 
||
| 75393 | 102  | 
sealed case class Date(rep: ZonedDateTime) {
 | 
| 64117 | 103  | 
def midnight: Date =  | 
104  | 
new Date(ZonedDateTime.of(rep.toLocalDate, LocalTime.MIDNIGHT, rep.getZone))  | 
|
105  | 
||
| 64111 | 106  | 
def to(zone: ZoneId): Date = new Date(rep.withZoneSameInstant(zone))  | 
107  | 
||
| 65733 | 108  | 
def unix_epoch: Long = rep.toEpochSecond  | 
| 65014 | 109  | 
def instant: Instant = Instant.from(rep)  | 
110  | 
def time: Time = Time.instant(instant)  | 
|
| 64057 | 111  | 
def timezone: ZoneId = rep.getZone  | 
112  | 
||
113  | 
def format(fmt: Date.Format = Date.Format.default): String = fmt(this)  | 
|
114  | 
override def toString: String = format()  | 
|
| 64056 | 115  | 
}  |