| author | desharna | 
| Sun, 18 Dec 2022 14:03:43 +0100 | |
| changeset 76682 | e260dabc88e6 | 
| 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: 
71601diff
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 | } |