#日期时间 #日期 #持续时间解析器 #科学 #纪元时间 #时间管理 #闰秒

无需 std hifitime

为科学应用提供超精确日期和时间处理,支持闰秒的 Rust

48 个版本 (稳定版)

4.0.0-dev2024年4月9日
3.9.0 2024年1月4日
3.8.6 2023年12月11日
3.8.5 2023年11月18日
0.0.1 2017年12月30日

#5日期和时间

Download history 1361/week @ 2024-05-04 1257/week @ 2024-05-11 1523/week @ 2024-05-18 1271/week @ 2024-05-25 1395/week @ 2024-06-01 1361/week @ 2024-06-08 1410/week @ 2024-06-15 1768/week @ 2024-06-22 1134/week @ 2024-06-29 1470/week @ 2024-07-06 1679/week @ 2024-07-13 1986/week @ 2024-07-20 2397/week @ 2024-07-27 2457/week @ 2024-08-03 2358/week @ 2024-08-10 1740/week @ 2024-08-17

9,370 每月下载量
31 个 crate 中使用 (21 直接使用)

Apache-2.0

375KB
7K SLoC

HiFitime 简介

HiFitime 是一个强大的 Rust 和 Python 库,用于时间管理。它提供广泛的功能,对不同时间尺度上的时间计算进行精确操作,适用于涉及广义相对论和时间膨胀的工程和科学应用。HiFitime 为 1900 年 1 月 1 日 TAI 附近 65,536 年内的精度提供纳秒级精度。HiFitime 还使用了 Kani 模型检查器 进行正式验证,了解更多信息请参阅 此处验证

大多数 HiFitime 用户只需依赖 EpochDuration 结构,以及可选的 Weekday 枚举进行基于周的运算。科学应用也可以使用 TimeScale 枚举。

用法

首先,在你的 Rust 项目中使用 cargo add hifitime 安装 hifitime,或者在 Python 中使用 pip install hifitime

如果从源代码构建,请注意,如果启用了 python 功能,则会构建 Python 包。

纪元("datetime" 对应物)

在不同时间尺度上创建纪元。

use hifitime::prelude::*;
use core::str::FromStr;
// Create an epoch in UTC
let epoch = Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 37);
// Or from a string
let epoch_from_str = Epoch::from_str("2000-02-29T14:57:29.000000037 UTC").unwrap();
assert_eq!(epoch, epoch_from_str);
// Creating it from TAI will effectively show the number of leap seconds in between UTC an TAI at that epoch
let epoch_tai = Epoch::from_gregorian_tai(2000, 2, 29, 14, 57, 29, 37);
// The difference between two epochs is a Duration
let num_leap_s = epoch - epoch_tai;
assert_eq!(format!("{num_leap_s}"), "32 s");

// Trivially convert to another time scale
// Either by grabbing a subdivision of time in that time scale
assert_eq!(epoch.to_gpst_days(), 7359.623402777777); // Compare to the GPS time scale

// Or by fetching the exact duration
let mjd_offset = Duration::from_str("51603 days 14 h 58 min 33 s 184 ms 37 ns").unwrap();
assert_eq!(epoch.to_mjd_tt_duration(), mjd_offset); // Compare to the modified Julian days in the Terrestrial Time time scale.

在 Python 中

>>> from hifitime import *
>>> epoch = Epoch("2000-02-29T14:57:29.000000037 UTC")
>>> epoch
2000-02-29T14:57:29.000000037 UTC
>>> epoch_tai = Epoch.init_from_gregorian_tai(2000, 2, 29, 14, 57, 29, 37)
>>> epoch_tai
2000-02-29T14:57:29.000000037 TAI
>>> epoch.timedelta(epoch_tai)
32 s
>>> epoch.to_gpst_days()
7359.623402777777
>>> epoch.to_mjd_tt_duration()
51603 days 14 h 58 min 33 s 184 ms 37 ns
>>> 

HiFitime 提供了 RFC2822、ISO8601 或 RFC3339 等多种日期时间格式。

use hifitime::efmt::consts::{ISO8601, RFC2822, RFC3339};
use hifitime::prelude::*;

let epoch = Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 37);
// The default Display shows the UTC time scale
assert_eq!(format!("{epoch}"), "2000-02-29T14:57:29.000000037 UTC");
// Format it in RFC 2822
let fmt = Formatter::new(epoch, RFC2822);
assert_eq!(format!("{fmt}"), format!("Tue, 29 Feb 2000 14:57:29"));

// Or in ISO8601
let fmt = Formatter::new(epoch, ISO8601);
assert_eq!(
    format!("{fmt}"),
    format!("2000-02-29T14:57:29.000000037 UTC")
);

// Which is somewhat similar to RFC3339
let fmt = Formatter::new(epoch, RFC3339);
assert_eq!(
    format!("{fmt}"),
    format!("2000-02-29T14:57:29.000000037+00:00")
);

需要一些自定义格式?HiFitime 还支持 C89 令牌,请参阅 文档

use core::str::FromStr;
use hifitime::prelude::*;

let epoch = Epoch::from_gregorian_utc_hms(2015, 2, 7, 11, 22, 33);

// Parsing with a custom format
assert_eq!(
    Epoch::from_format_str("Sat, 07 Feb 2015 11:22:33", "%a, %d %b %Y %H:%M:%S").unwrap(),
    epoch
);

// And printing with a custom format
let fmt = Format::from_str("%a, %d %b %Y %H:%M:%S").unwrap();
assert_eq!(
    format!("{}", Formatter::new(epoch, fmt)),
    "Sat, 07 Feb 2015 11:22:33"
);

如果您启用了 std 功能(默认),还可以获取当前系统的 UTC 时间,并找到下一个或上一个星期日。

use hifitime::prelude::*;

#[cfg(feature = "std")]
{
    let now = Epoch::now().unwrap();
    println!("{}", now.next(Weekday::Tuesday));
    println!("{}", now.previous(Weekday::Sunday));
}

我们经常需要在两个纪元之间固定步骤查询某些内容。HiFitime 通过 TimeSeries 使这变得非常简单。

use hifitime::prelude::*;

let start = Epoch::from_gregorian_utc_at_midnight(2017, 1, 14);
let end = start + 12.hours();
let step = 2.hours();

let time_series = TimeSeries::inclusive(start, end, step);
let mut cnt = 0;
for epoch in time_series {
    #[cfg(feature = "std")]
    println!("{}", epoch);
    cnt += 1
}
// Check that there are indeed seven two-hour periods in a half a day,
// including start and end times.
assert_eq!(cnt, 7)

在 Python 中

>>> from hifitime import *
>>> start = Epoch.init_from_gregorian_utc_at_midnight(2017, 1, 14)
>>> end = start + Unit.Hour*12
>>> iterator = TimeSeries(start, end, step=Unit.Hour*2, inclusive=True)
>>> for epoch in iterator:
...     print(epoch)
... 
2017-01-14T00:00:00 UTC
2017-01-14T02:00:00 UTC
2017-01-14T04:00:00 UTC
2017-01-14T06:00:00 UTC
2017-01-14T08:00:00 UTC
2017-01-14T10:00:00 UTC
2017-01-14T12:00:00 UTC
>>> 

持续时间

use hifitime::prelude::*;
use core::str::FromStr;

// Create a duration using the `TimeUnits` helping trait.
let d = 5.minutes() + 7.minutes() + 35.nanoseconds();
assert_eq!(format!("{d}"), "12 min 35 ns");

// Or using the built-in enums
let d_enum = 12 * Unit::Minute + 35.0 * Unit::Nanosecond;

// But it can also be created from a string
let d_from_str = Duration::from_str("12 min 35 ns").unwrap();
assert_eq!(d, d_from_str);

HiFitime 保证纳秒级精度,但大多数人类应用程序并不太关心这一点。持续时间可以四舍五入以提供对人类有用的近似值。

use hifitime::prelude::*;

// Create a duration using the `TimeUnits` helping trait.
let d = 5.minutes() + 7.minutes() + 35.nanoseconds();
// Round to the nearest minute
let rounded = d.round(1.minutes());
assert_eq!(format!("{rounded}"), "12 min");

// And this works on Epochs as well.
let previous_post = Epoch::from_gregorian_utc_hms(2015, 2, 7, 11, 22, 33);
let example_now = Epoch::from_gregorian_utc_hms(2015, 8, 17, 22, 55, 01);

// We'll round to the nearest fifteen days
let this_much_ago = example_now - previous_post;
assert_eq!(format!("{this_much_ago}"), "191 days 11 h 32 min 28 s");
let about_this_much_ago_floor = this_much_ago.floor(15.days());
assert_eq!(format!("{about_this_much_ago_floor}"), "180 days");
let about_this_much_ago_ceil = this_much_ago.ceil(15.days());
assert_eq!(format!("{about_this_much_ago_ceil}"), "195 days");

在 Python 中

>>> from hifitime import *
>>> d = Duration("12 min 32 ns")
>>> d.round(Unit.Minute*1)
12 min
>>> d
12 min 32 ns
>>> 

hifitime on crates.io hifitime on docs.rs minimum rustc: 1.70 Build Status Build Status codecov

timechrono 的比较

首先,timechrono 都是非常出色的库。它们之所以拥有数百万的下载量,是有原因的。其次,hifitime 项目始于 2017 年 10 月,比 time 的复兴时间(约 2019 年)早得多。

两者之间的一个关键区别是,chronotime 都将“时间”和“日期”的概念分开。hifitime 认为这是在物理上不成立的:时间和日期都是相对于给定时间尺度中参考点的偏移。这就是为什么 hifitime 不会将组成日期的各个部分分开,而是只存储相对于 TAI 的固定持续时间。此外,Hifitime 使用模型检查器进行了形式化验证,这比属性测试要彻底得多。

更重要的是,timechrono 都不适用于天文学、天体力学或任何必须考虑相对论速度引起的时间膨胀或缺乏地球作为重力源(这决定了“秒”的“滴答”)的物理。

如果使用 ut1 功能构建,Hifitime 还原生支持 UT1 时间尺度(唯一“真实”的时间)。

特性

  • 从 UTC 的系统时间初始化高精度纪元
  • 闰秒(如 IETF 每年宣布的那样)
  • UTC 表示形式,支持 ISO8601 和 RFC3339 格式,解析速度快(45 纳秒)
  • 简单的时序运算支持:加法(例如 2.hours() + 3.seconds()),减法(例如 2.hours() - 3.seconds()),舍入/向下取整/向上取整操作(例如 2.hours().round(3.seconds())
  • 支持纪元和时序的范围内(EpochDuration 的 linspace)
  • 轻松转换许多时间尺度
  • ESA 的 Navipedia 进行高保真度历书时间/动态地心坐标系时间(TDB)计算
  • 儒略日和改正儒略日
  • 对嵌入式设备友好:尽可能使用 no-stdconst fn

该库已针对 NASA/NAIF SPICE 进行验证,以进行历书时间到协调世界时(UTC)的计算:从 1972 年 1 月 1 日起,SPICE 和 hifitime 在计算 ET 和 UTC 方面没有差异(零纳秒)。有关闰秒的详细信息,请参阅 闰秒 部分。其他示例使用外部参考进行了验证,具体细节将在逐个测试的基础上进行说明。

支持的时间尺度

  • 国际原子时(TAI)
  • 协调世界时(UTC)
  • 地球时(TT)
  • 历书时间(ET),按照 NASA/NAIF SPICE 闰秒内核,没有小的扰动
  • 动态地心坐标系时间(TDB),一种更高保真的历书时间
  • 全球定位系统时间(GPST)
  • 伽利略系统时间(GST)
  • 北斗时间(BDT)
  • UNIX

不支持的功能

  • 时间无关/仅日期纪元。HiFTime仅支持日期和时间的组合,但提供了以下辅助函数:Epoch::{at_midnight, at_noon}

设计

没有软件是完美的,请在Github上报告任何问题或错误。

持续时间

在底层,持续时间表示为一个16位有符号整数(i16)和一个64位无符号整数(u64),表示过去一个世纪以来的纳秒数。纳秒的溢出和下溢通过改变世纪的数目来处理,以确保纳秒数永远不会表示超过一个世纪(64位可以存储略超过四个世纪的纳秒数)。

优势

  1. 持续时间的精确精度:使用浮点值会导致长时间(例如儒略日)的持续时间比短时间有更少的精度。HiFTime中的持续时间具有65,536年的精度,精确到一纳秒。
  2. 跳过浮点运算允许此库在无浮点单元的嵌入式设备中使用。
  3. 持续时间算术是精确的,例如,一小时的三分之一正好是二十分钟,而不是“0.33333小时”。

缺点

  1. 大多数天体动力学应用都需要计算浮点值形式的持续时间,例如查询历书。根据基准测试,这种设计会导致大约5.2纳秒的开销(Duration to f64 seconds基准)。您可以使用cargo bench运行基准测试。

纪元

纪元简单地是一个Duration的包装器。所有纪元都存储在相对于1900年1月1日中午(官方TAI纪元)的TAI持续时间中。选择TAI符合基本天文学标准(SOFA)的建议,即选择无故障的时间尺度(即没有像闰秒或非均匀秒(如TDB)这样的不连续性)。

打印和解析

纪元可以按以下时间尺度进行格式化和解析

  • UTC: {epoch}
  • TAI: {epoch:x}
  • TT: {epoch:X}
  • TDB: {epoch:e}
  • ET: {epoch:E}
  • UNIX: {epoch:p}
  • GPS: {epoch:o}

闰秒支持

闰秒允许TAI(绝对时间参考)和UTC(民用时间参考)不会漂移太多。简而言之,UTC允许人们在中午看到太阳在正午,而TAI则不考虑这一点。引入闰秒是为了使UTC能够赶上TAI的绝对时间参考。具体来说,UTC时钟“停止”一秒钟来弥补TAI和UTC之间累积的差异。这些闰秒由IERS提前几个月宣布,请参阅IETF闰秒参考

这些闰秒在UTC日期格式中的“放置”由软件决定:没有通用的处理方式。一些软件防止出现秒数跳动,即在23:59:59时,UTC时钟会连续跳动两秒(而不是一秒)然后跳到00:00:00。一些软件,如hifitime,允许在严格插入闰秒的日期上格式化UTC日期为23:59:60。例如,日期 2016-12-31 23:59:60 UTC 在hifitime中是有效日期,因为2017年1月1日插入了闰秒。

重要

在第一个闰秒之前,NAIF SPICE声称TAI和UTC之间有九秒的差异:这与基本天文学标准(SOFA)不同。SOFA的iauDat函数将从1960年到1972年返回非整数的闰秒。对于1960年之前的日期,它将返回错误。hifitime只计算IERS宣布的闰秒:1972年1月1日TAI和UTC之间有一个十秒的跳跃。这使得UNIX时间的计算在hifitime中成为TAI的特定偏移量。然而,SOFA返回的史前(1972年之前)闰秒可以在将iers_only参数设置为false时,通过纪元方法的leap_seconds函数获得。

恒星时与动态地心时(TDB)

理论上,截至2000年1月,ET和TDB现在应该相同。然而,NASA NAIF闰秒文件(例如naif00012.tls)使用简化的算法来计算TDB

方程[4],它忽略了小周期波动,准确到约0.000030秒。

为了与NAIF完全兼容,hifitime使用NAIF算法计算“恒星时”和ESA算法计算“动态地心时”。因此,如果需要精确的NAIF行为,请使用标记为et的所有函数,而不是tdb函数,例如使用epoch.to_et_seconds()而不是epoch.to_tdb_seconds()

更改日志

4.0.0(WIP)

  • 最低支持Rust版本(MSRV)提升到1.77.0
  • 对代码进行重大重构,以简化维护并从3.x中移除已弃用函数
  • 将所有时间尺度转换集中到to_time_scale函数中 -- 由@gwbres做出的巨大努力
  • 移除了Epoch和Duration的der编码/解码

3.9.0

3.8.5

从3.8.2到此次发布的更改仅是依赖项升级。

最低支持的Rust版本从1.64提升到1.70

3.8.2

  • 澄清README并添加一个与timechrono进行比较的章节,参见#221
  • 修复了1900年之前不正确的格里历日期打印,参见#204

3.8.1(未发布)

  • 修复了格式化器的文档,参考:#202
  • 将MSRV更新到1.59以适配rayon v 1.10

3.8.0

感谢@gwbres在此版本中做出的工作!

  • 修复了正式验证的CI和上传的工件,参考:#179
  • 引入了由@gwbres实现的每周时间构建和转换,参考:#180#188
  • @gwbres修复了src/timeunits.rs中的小错误,参考:#189
  • 显著扩展了DurationEpoch的正式验证,并引入了kani::ArbitraryDurationEpoch,以便用户可以正式验证其时间的使用,参考:#192
  • 现在可以使用LeapSecondsFile::from_path指定一个闰秒文件(IERS格式),(需要读取文件的std功能),参考:#43
  • 现在支持UT1时间尺度!您必须使用来自JPL地球定向参数的数据构建一个Ut1Provider结构,或者只需使用Ut1Provider::download_short_from_jpl()来自动从NASA JPL下载数据。
  • strptimestrftime的C89等价物现在支持,参考:#181。请参阅文档了解重要限制和如何构建自定义格式化器。
  • 现在支持ISO年日和年内日初始化一个纪元(提供时间尺度和年份)和格式化,参考:#182
  • Python:纪元的表示现在是在其初始化的时间尺度中

3.7.0

@gwbres表示衷心的感谢,他为此版本投入了所有的工作。这些可用性更改允许Rinex使用hifitime,查看这项工作。

3.6.0

  • 现在支持伽利略系统时间和北斗时间,感谢 @gwbres 所做的一切工作!
  • 从格里高利历表示初始化纪元的速度显著提高,感谢 @conradludgate,详见 #160
  • 纪元和持续时间现在都有了 minmax 函数,分别返回纪元/持续时间的一个副本,这个副本在 selfother 之间是最小或最大的,参看 #164
  • [Python] 持续时间和纪元现在支持运算符 >>=<<===!=。纪元现在支持 init_from_gregorian,就像在 Rust 中一样。持续时间也可以使用 timedelta 函数从一个持续时间减去另一个持续时间,参看 #162
  • 现在可以在不同的时间尺度上格式化时间序列,参看 #163

3.5.0

  • 纪元现在存储了它们定义的时间尺度:这允许在各自的时间尺度上添加持续时间。例如,当纪元在 UTC 中初始化时,将 36 小时添加到 1971-12-31 中午的纪元,将得到一个与在 TAI 中初始化的同一时间添加相同持续时间的纪元不同的结果(因为 IERS 公布的第一个闰秒是在 1972-01-01),参看 test_add_durations_over_leap_seconds 测试。
  • 完全支持 RFC3339 和 ISO8601 初始化纪元,包括偏移量,例如 Epoch::from_str("1994-11-05T08:15:30-05:00"),参看 #73
  • Python 包可在 PyPI 上找到!要构建 Python 包,您必须首先安装 maturin,然后使用 python 功能标志进行构建。例如,maturin develop -F python && python 将以调试模式构建 Python 包并启动一个新的 shell,在该 shell 中可以导入该包。
  • 修复了打印 Duration::MIN(或任何世纪数最小化的持续时间)时的错误。
  • 现在可以格式化时间序列
  • 纪元现在可以根据它们初始化的时间尺度进行向上取整、向下取整和四舍五入,参看 #145
  • 当指定时间系统时,纪元现在可以从格里高利历初始化:from_gregorianfrom_gregorian_hmsfrom_gregorian_at_noonfrom_gregorian_at_midnight
  • 修复了在操作非常接近 Duration::MIN(即负三十二世纪)的持续时间时的错误。
  • 持续时间解析现在支持字符串中的多个单位,并且不使用正则表达式。这允许它与 no-std 一起工作。
  • 纪元解析不再需要 regex
  • 函数不再那么符合惯例:所有的 as_* 函数变成了 to_*,而 in_* 也变成了 to_*,参看 #155

3.4.0

  • 恒星历时间和动力学地心时间现在固定使用J2000参考历元,而不是J1900参考历元。如果您依赖于将ET/TDB转换为UTC时之前一个世纪的误差,这将是一个可能破坏性的变更。如果使用原始表示,则没有差异。
  • 恒星历现在与NAIF SPICE严格匹配:SPICE和hifitime之间的误差现在是零纳秒。这是在引入第一个闰秒之后。在第一个闰秒之前,NAIF SPICE声称TAI和UTC之间存在九秒的差异:这与SOFA不同。hifitime在史前(1972年之前)的计算中根本不考虑闰秒。
  • 1960年至1972年的天文学基础标准 (SOFA)闰秒现在可以通过Epoch实例上的leap_seconds() -> Option<f64>函数获得。重要的是,这里不应该注意到hifitime的行为有任何差异:hifitime在所有计算中都忽略了史前的闰秒,并且只提供以符合SOFA的计算。
  • EpochDuration现在具有C内存表示,以便更容易地将hifitime嵌入C。
  • EpochDuration现在可以使用asn1der crate功能进行编码或解码为ASN1 DER(默认情况下禁用)。

3.3.0

  • Duration上的归一化操作进行了正式验证,这又保证了Epoch操作不会恐慌,参见#127
  • 修复了TimeSerieslensize_hint,参见#131,由@d3v-null报告,感谢您的发现!
  • Epoch现在实现了EqOrd,参见#133,感谢@mkolopanis提交的PR!
  • Epoch现在可以以不同的时间系统使用格式修饰符打印,参见#130
  • (次要) as_utc_durationEpoch中现在是公开的,参见#129
  • (次要) 整个crate现在使用num-traits,因此避免了显式使用libm。基本上,对f64的操作看起来像正常的Rust,参见#128
  • (次要) 将测试移动到自己的文件夹,以清楚地表明已进行了彻底的测试

3.2.0

  • 通过使用libm对非核心f64操作进行修复,以解决no-std实现
  • 添加UNIX时间戳,感谢@mkolopanis
  • 枚举现在继承Eq,其中一些继承Ord(在相关的地方),参见#118
  • 尽可能使用const fn,并在可能的情况下切换到引用,参见#119
  • 现在可以通过to_parts和to_tai_parts分别提取DurationEpoch的世纪和纳秒#122
  • ceilfloorround 操作添加到 EpochDuration

3.1.0

  • 添加 #![no_std] 支持
  • to_parts 添加到 Duration 以提取持续时间的世纪和纳秒
  • 允许从 TAI 系统中的持续时间及其部分构建 Epoch
  • 为 GPS 添加纯纳秒(u64)构造函数和获取器,因为基于 GPS 的时钟将以纳秒计数

可能破坏性的变更

  • Errors::ParseError 不再包含 String,而是包含枚举 ParsingErrors。这被认为是可能破坏性的,因为只有在捕获和处理日期时间解析或单位解析时才会破坏代码(不常见)。此外,输出仍然是可显示的。

3.0.0

  • 从 TwoFloat 重写后端为 i16 的世纪和 u64 的纳秒的结构。感谢 @pwnorbitals 在 #107 中提出想法并编写了原型。这至少将大多数计算的速度提高了 2 倍,参见 此评论
  • @cjordan 修复 GPS 域,并在 Epoch 中添加辅助函数

版本策略的重要更新

我们想通知用户 Hifitime 版本策略的重要变化。从版本 3.9.0 开始,次要版本更新可能包括可能破坏向后兼容性的更改。虽然我们努力保持稳定并最小化干扰,但这种变化使我们能够融入重大的改进并更快地适应不断发展的用户需求。我们建议用户仔细阅读每次更新的发行说明,即使是次要更新,也要了解其现有实现可能产生的任何潜在影响。我们提供稳健且动态的时间管理库的承诺始终不变,我们相信这种版本变化将更好地满足我们社区不断发展的需求。

开发

感谢您考虑为 Hifitime 提供帮助!

Rust 开发需要 cargo 以及为最低支持的 Rust 版本提供的构建工具。

Python 开发

首先,请安装 maturin 并设置一个 Python 虚拟环境,从中进行开发。同时确保 Cargo.toml 中的包版本大于任何已发布的版本。例如,如果 PyPi 上发布的最新版本是 4.0.0-a.0(alpha-0),请确保您更改 Cargo.toml 文件,使其至少在版本 alpha-1 或更高。否则,pip install 将从 PyPi 下载而不是从本地文件夹安装。要运行 Python 测试,您必须在虚拟环境中安装 pytest

具体步骤如下

  1. 进入虚拟环境:source .venv/bin/activate(例如)
  2. 确保已安装 pytest:pip install pytest
  3. 构建 hifitime 并指定 Python egg 的输出文件夹:maturin build -F python --out dist
  4. 安装 egg: pip install dist/hifitime-4.0.0.dev1-cp311-cp311-linux_x86_64.whl
  5. 使用环境 pytest 运行测试: .venv/bin/pytest

依赖项

~2–16MB
~205K SLoC